diff --git a/README.md b/README.md index 90394b0a40..6cd223086d 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,9 @@ - [x] 카드 덱 생성하기 - [x] 플레이어 이름 입력 받기 - [x] 쉼표로 분리 + - [x] 중복이 있는지 체크 +- [x] 플레이어 별로 베팅 금액 입력 받기 + - [x] 숫자가 아니면 예외 발생 - [x] 딜러 및 플레이어 생성하기 - [x] 딜러 및 플레이어들에게 카드를 각각 두 장씩 주기 - [x] 딜러의 카드는 첫 장만 공개 @@ -24,7 +27,15 @@ - [x] 플레이어의 숫자가 21을 넘기면 무조건 플레이어의 패배 - [x] 플레이어의 숫자가 딜러보다 21에 가까우면 플레이어의 승리 - [x] 플레이어의 숫자가 21을 넘기지 않고 딜러의 숫자가 21을 넘기면 플레이어의 승리 - - [x] 최종 승패 결과를 출력 + - [x] 같은 21이어도 블랙잭(2장)이 승리 + - [x] ~~최종 승패 결과를 출력~~ +- [x] 베팅 계산 + - [x] 일반적인 상황에 대한 계산 + - [x] 이기면 베팅 금액 만큼 수익 + - [x] 지면 베팅 금액 만큼 손실 + - [x] 비기면 베팅 금액을 돌려받음 + - [x] 블랙잭으로 이기면 1.5배의 수익 + - [x] 최종 수익을 출력 --- @@ -34,13 +45,18 @@ - 게임의 딜러 - 카드를 소유하고 있다. - 카드 덱을 받아서 보유 카드 숫자가 17이 넘을 때 까지 카드를 뽑는다. -- Player - - 게임의 플레이어 - - 카드를 소유하고 있다. - - 카드 덱을 받아서 카드를 뽑는다. -- Name - - 딜러 또는 플레이어의 이름 - - null이거나 빈 문자열일 수 없다. +- Players + - Player들을 모아놓은 일급컬렉션 + - PlayerNames + - Name들을 포장한 객체 + - 플레이어 이름의 중복 여부와 인원 수를 검증한다. + - Name + - 딜러 또는 플레이어의 이름 + - null이거나 빈 문자열일 수 없다. + - Player + - 게임의 플레이어 + - 카드를 소유하고 있다. + - 카드 덱을 받아서 카드를 뽑는다. - Card - Pattern - 카드의 문양이 저장된 enum @@ -49,16 +65,41 @@ - 카드의 문양과 끗수를 저장하고 있다. - CardDeck - 게임에서 사용하는 카드들을 관리하고 있다. - - CardsGenerator - - 카드를 생성하는 전략을 제공하는 인터페이스 - - BlackJackCardsGenerator - - CardsGenerator의 구현체로 서로 다른 52장의 카드를 가진 덱을 생성한다. -- Rule - - 게임의 규칙을 담고 있다. - - 카드의 총합을 계산한다. - - 버스트(총합이 21을 넘는 경우) 여부를 판단한다. -- Result +- Outcome + - 게임의 승, 무, 패 + - 플레이어를 기준으로 딜러와 비교해서 판단한다. +- Betting + - 플레이어의 베팅 정보를 저장한다. +- Outcome + - 승, 무, 패 값을 가진 enum +- GameResult - 플레이어와 딜러의 카드를 받는다. - 승패를 결정한다. - - Judgement - - 승, 무, 패 값을 가진 enum \ No newline at end of file + - 승패를 바탕으로 수익을 저장한다. + - 플레이어의 수익을 바탕으로 딜러의 수익을 반환한다. + +--- + +### 게임 상태 정리 + +- State + - 모든 상태의 상위 인터페이스 +- AbstractState + - 하위 상태들의 공통 로직을 모아서 구현해 놓은 추상 클래스 +- Running + - 게임이 진행 중인 상태 + - hit 또는 stand 가능 + - DealerRunning + - 딜러의 Running 상태 + - 17을 기준으로 hit / stand 여부를 판단하는 로직을 가짐 + - PlayerRunning + - 플레이어의 Running 상태 +- Stand + - 기존 상태에서 stand한 상태 + - 게임 진행 불가 +- Blackjack + - 최초 started 상태에서 점수 계산 시 21이 나온 상태 + - 게임 진행 불가 +- Bust + - hit을 한 이후 점수 합이 21을 넘은 상태 + - 게임 진행 불가 diff --git a/src/main/java/blackjack/BlackJackApplication.java b/src/main/java/blackjack/BlackJackApplication.java deleted file mode 100644 index d53c5fb430..0000000000 --- a/src/main/java/blackjack/BlackJackApplication.java +++ /dev/null @@ -1,9 +0,0 @@ -package blackjack; - -public class BlackJackApplication { - - public static void main(String[] args) { - BlackJackGame blackJackGame = new BlackJackGame(); - blackJackGame.play(); - } -} diff --git a/src/main/java/blackjack/BlackjackApplication.java b/src/main/java/blackjack/BlackjackApplication.java new file mode 100644 index 0000000000..5f07f1b158 --- /dev/null +++ b/src/main/java/blackjack/BlackjackApplication.java @@ -0,0 +1,9 @@ +package blackjack; + +public class BlackjackApplication { + + public static void main(String[] args) { + BlackjackGame blackjackGame = new BlackjackGame(); + blackjackGame.play(); + } +} diff --git a/src/main/java/blackjack/BlackJackGame.java b/src/main/java/blackjack/BlackjackGame.java similarity index 50% rename from src/main/java/blackjack/BlackJackGame.java rename to src/main/java/blackjack/BlackjackGame.java index 3076875d43..fccfeef4c4 100644 --- a/src/main/java/blackjack/BlackJackGame.java +++ b/src/main/java/blackjack/BlackjackGame.java @@ -1,10 +1,13 @@ package blackjack; +import blackjack.domain.Betting; import blackjack.domain.GameResult; -import blackjack.domain.Name; import blackjack.domain.card.CardDeck; import blackjack.domain.participant.Dealer; +import blackjack.domain.participant.Name; import blackjack.domain.participant.Player; +import blackjack.domain.participant.PlayerNames; +import blackjack.domain.participant.Players; import blackjack.dto.HitRequest; import blackjack.dto.ParticipantInitialResponse; import blackjack.dto.ParticipantResponse; @@ -13,41 +16,51 @@ import java.util.List; import java.util.stream.Collectors; -public class BlackJackGame { +public class BlackjackGame { public void play() { - CardDeck deck = new CardDeck(); + CardDeck deck = CardDeck.createGameDeck(); Dealer dealer = new Dealer(deck.drawDouble()); - List players = createPlayers(inputPlayerNames(), deck); + Players players = createPlayers(inputPlayerNames(), deck); + proceed(deck, dealer, players); } - private List inputPlayerNames() { + private PlayerNames inputPlayerNames() { try { - return InputView.inputPlayerNames() - .stream() - .map(Name::new) - .collect(Collectors.toUnmodifiableList()); + return new PlayerNames(InputView.inputPlayerNames()); } catch (IllegalArgumentException e) { OutputView.printErrorMessage(e.getMessage()); return inputPlayerNames(); } } - private List createPlayers(List playerNames, CardDeck deck) { - return playerNames.stream() - .map(name -> new Player(name, deck.drawDouble())) - .collect(Collectors.toUnmodifiableList()); + private Players createPlayers(PlayerNames names, CardDeck deck) { + try { + return new Players(names, deck, this::inputBetting); + } catch (IllegalArgumentException e) { + OutputView.printErrorMessage(e.getMessage()); + return createPlayers(names, deck); + } + } + + private Betting inputBetting(Name name) { + try { + return new Betting(InputView.inputBetMoney(name.getValue())); + } catch (IllegalArgumentException e) { + OutputView.printErrorMessage(e.getMessage()); + return inputBetting(name); + } } - private void proceed(CardDeck deck, Dealer dealer, List players) { + private void proceed(CardDeck deck, Dealer dealer, Players players) { alertStart(dealer, players); proceedPlayers(players, deck); proceedDealer(dealer, deck); showResult(dealer, players); } - private void alertStart(Dealer dealer, List players) { + private void alertStart(Dealer dealer, Players players) { ParticipantInitialResponse dealerResponse = new ParticipantInitialResponse(dealer); List playerResponses = players.stream() .map(ParticipantResponse::new) @@ -56,21 +69,30 @@ private void alertStart(Dealer dealer, List players) { OutputView.printStartMessage(dealerResponse, playerResponses); } - private void proceedPlayers(List players, CardDeck deck) { + private void proceedPlayers(Players players, CardDeck deck) { players.forEach(player -> proceedPlayer(player, deck)); } private void proceedPlayer(Player player, CardDeck deck) { - while (player.isHittable() && inputHitRequest(player) == HitRequest.YES) { - player.hit(deck); - OutputView.printParticipantCards(new ParticipantResponse(player)); + while (!player.isFinished()) { + proceedOnce(player, deck); } showStopReason(player); } + private void proceedOnce(Player player, CardDeck deck) { + if (inputHitRequest(player) == HitRequest.YES) { + player.hit(deck); + OutputView.printPlayerCards(new ParticipantResponse(player)); + return; + } + player.stand(); + OutputView.printPlayerCards(new ParticipantResponse(player)); + } + private HitRequest inputHitRequest(Player player) { try { - return HitRequest.find(InputView.inputHitRequest(player.getName())); + return InputView.inputHitRequest(player.getName()); } catch (IllegalArgumentException e) { OutputView.printErrorMessage(e.getMessage()); return inputHitRequest(player); @@ -78,7 +100,7 @@ private HitRequest inputHitRequest(Player player) { } private void showStopReason(Player player) { - if (player.isBlackJack()) { + if (player.isBlackjack()) { OutputView.printBlackJackMessage(player.getName()); return; } @@ -88,16 +110,18 @@ private void showStopReason(Player player) { } private void proceedDealer(Dealer dealer, CardDeck deck) { - while (dealer.shouldHit()) { + while (!dealer.isFinished()) { dealer.hit(deck); OutputView.printDealerHitMessage(new ParticipantResponse(dealer)); } } - private void showResult(Dealer dealer, List players) { + private void showResult(Dealer dealer, Players players) { OutputView.printCardResultMessage(); - OutputView.printParticipantCards(new ParticipantResponse(dealer)); - players.forEach(player -> OutputView.printParticipantCards(new ParticipantResponse(player))); - OutputView.printWinResult(GameResult.of(dealer, players)); + OutputView.printPlayerCards(new ParticipantResponse(dealer)); + OutputView.printPlayersCards(players.stream() + .map(ParticipantResponse::new) + .collect(Collectors.toUnmodifiableList())); + OutputView.printGameResult(GameResult.of(dealer, players)); } } diff --git a/src/main/java/blackjack/domain/Betting.java b/src/main/java/blackjack/domain/Betting.java new file mode 100644 index 0000000000..f27acaf5b2 --- /dev/null +++ b/src/main/java/blackjack/domain/Betting.java @@ -0,0 +1,21 @@ +package blackjack.domain; + +public class Betting { + + private final int betMoney; + + public Betting(int betMoney) { + validate(betMoney); + this.betMoney = betMoney; + } + + private void validate(int betMoney) { + if (betMoney <= 0) { + throw new IllegalArgumentException("[ERROR] 베팅 금액은 0원 이하일 수 없습니다."); + } + } + + public int getBetMoney() { + return betMoney; + } +} diff --git a/src/main/java/blackjack/domain/GameResult.java b/src/main/java/blackjack/domain/GameResult.java index 0c03b9766b..54f032d1dd 100644 --- a/src/main/java/blackjack/domain/GameResult.java +++ b/src/main/java/blackjack/domain/GameResult.java @@ -2,47 +2,38 @@ import blackjack.domain.participant.Dealer; import blackjack.domain.participant.Player; -import java.util.Arrays; +import blackjack.domain.participant.Players; import java.util.Collections; -import java.util.EnumMap; import java.util.LinkedHashMap; -import java.util.List; import java.util.Map; public class GameResult { - private final Map dealerResult; - private final Map playersResult; + private final Map profits; - private GameResult(Map dealerResult, Map playersResult) { - this.dealerResult = Collections.unmodifiableMap(new EnumMap<>(dealerResult)); - this.playersResult = Collections.unmodifiableMap(new LinkedHashMap<>(playersResult)); + public GameResult(Map profits) { + this.profits = Collections.unmodifiableMap(new LinkedHashMap<>(profits)); } - public static GameResult of(Dealer dealer, List players) { - Map dealerResult = new EnumMap<>(Outcome.class); - Map playersResult = new LinkedHashMap<>(); + public static GameResult of(Dealer dealer, Players players) { + Map playersProfits = new LinkedHashMap<>(); - initDealerResult(dealerResult); for (Player player : players) { - Outcome playerOutcome = Outcome.judge(player, dealer); - playersResult.put(player.getName(), playerOutcome); - dealerResult.merge(playerOutcome.getOpposite(), 1, Integer::sum); + Outcome outcome = Outcome.judge(player, dealer); + playersProfits.put(player.getName(), player.calculateProfit(outcome)); } - return new GameResult(dealerResult, playersResult); + return new GameResult(playersProfits); } - private static void initDealerResult(Map dealerResult) { - Arrays.stream(Outcome.values()) - .forEach(value -> dealerResult.put(value, 0)); + public int getDealerProfit() { + return profits.values() + .stream() + .mapToInt(profit -> profit * (-1)) + .sum(); } - public Map getDealerResult() { - return dealerResult; - } - - public Map getPlayersResult() { - return playersResult; + public Map getProfits() { + return profits; } } diff --git a/src/main/java/blackjack/domain/Outcome.java b/src/main/java/blackjack/domain/Outcome.java index 0340a6d42f..45684b0d2b 100644 --- a/src/main/java/blackjack/domain/Outcome.java +++ b/src/main/java/blackjack/domain/Outcome.java @@ -4,50 +4,41 @@ import blackjack.domain.participant.Player; public enum Outcome { - WIN("승"), - DRAW("무"), - LOSE("패"); + WIN(1), + DRAW(0), + LOSE(-1), + WIN_BLACKJACK(1.5); - private final String name; + private final double profitRate; - Outcome(String name) { - this.name = name; + Outcome(double profitRate) { + this.profitRate = profitRate; } public static Outcome judge(Player player, Dealer dealer) { - if (player.isBust() || !player.isBlackJack() && dealer.isBlackJack()) { - return Outcome.LOSE; + if (player.isBlackjack() && !dealer.isBlackjack()) { + return WIN_BLACKJACK; } - if (dealer.isBust() || player.isBlackJack() && !dealer.isBlackJack()) { - return Outcome.WIN; + if (player.isBust() || !player.isBlackjack() && dealer.isBlackjack()) { + return LOSE; } - if (player.isBlackJack() && dealer.isBlackJack()) { - return Outcome.DRAW; + if (dealer.isBust()) { + return WIN; } return judgeByScore(player.getScore(), dealer.getScore()); } - private static Outcome judgeByScore(int score, int target) { - if (score > target) { - return Outcome.WIN; - } - if (score == target) { - return Outcome.DRAW; - } - return Outcome.LOSE; - } - - public Outcome getOpposite() { - if (this == WIN) { - return LOSE; - } - if (this == LOSE) { + private static Outcome judgeByScore(Score score, Score target) { + if (score.isGreaterThan(target)) { return WIN; } - return DRAW; + if (score.equals(target)) { + return DRAW; + } + return LOSE; } - public String getName() { - return name; + public double getProfitRate() { + return profitRate; } } diff --git a/src/main/java/blackjack/domain/Score.java b/src/main/java/blackjack/domain/Score.java index 22512302f1..9d0a2c379d 100644 --- a/src/main/java/blackjack/domain/Score.java +++ b/src/main/java/blackjack/domain/Score.java @@ -12,6 +12,18 @@ public int getValue() { return value; } + public boolean isLessThan(int value) { + return this.value < value; + } + + public boolean isGreaterThan(int value) { + return this.value > value; + } + + public boolean isGreaterThan(Score target) { + return target.isLessThan(value); + } + @Override public boolean equals(Object o) { if (this == o) { diff --git a/src/main/java/blackjack/domain/State.java b/src/main/java/blackjack/domain/State.java deleted file mode 100644 index 40d76bfce2..0000000000 --- a/src/main/java/blackjack/domain/State.java +++ /dev/null @@ -1,26 +0,0 @@ -package blackjack.domain; - -import blackjack.domain.card.Cards; -import java.util.Arrays; -import java.util.function.Predicate; - -public enum State { - - BUST(cards -> cards.calculateScore().getValue() > 21), - BLACKJACK(cards -> cards.calculateScore().getValue() == 21 && cards.getCards().size() == 2), - NOTHING(cards -> true), - ; - - private final Predicate isMatch; - - State(Predicate isMatch) { - this.isMatch = isMatch; - } - - public static State from(Cards cards) { - return Arrays.stream(values()) - .filter(state -> state.isMatch.test(cards)) - .findFirst() - .orElseThrow(() -> new IllegalArgumentException("[ERROR] 조건에 맞지 않습니다.")); - } -} diff --git a/src/main/java/blackjack/domain/card/Card.java b/src/main/java/blackjack/domain/card/Card.java index e47061c8b9..a4e4334b58 100644 --- a/src/main/java/blackjack/domain/card/Card.java +++ b/src/main/java/blackjack/domain/card/Card.java @@ -1,15 +1,46 @@ package blackjack.domain.card; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.stream.Collectors; + public class Card { + private static final List pool = createCardPool(); + private final Pattern pattern; private final Denomination denomination; - public Card(Pattern pattern, Denomination denomination) { + private Card(Pattern pattern, Denomination denomination) { this.pattern = pattern; this.denomination = denomination; } + public static Card of(Pattern pattern, Denomination denomination) { + return pool.stream() + .filter(card -> card.pattern == pattern && card.denomination == denomination) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("[ERROR] 올바르지 않은 카드입니다.")); + } + + private static List createCardPool() { + return Arrays.stream(Pattern.values()) + .map(Card::createCardsBy) + .flatMap(Collection::stream) + .collect(Collectors.toList()); + } + + private static List createCardsBy(Pattern pattern) { + return Arrays.stream(Denomination.values()) + .map(denomination -> new Card(pattern, denomination)) + .collect(Collectors.toList()); + } + + public static List getPool() { + return pool; + } + public Pattern getPattern() { return pattern; } diff --git a/src/main/java/blackjack/domain/card/CardDeck.java b/src/main/java/blackjack/domain/card/CardDeck.java index 159474e891..195739f9ac 100644 --- a/src/main/java/blackjack/domain/card/CardDeck.java +++ b/src/main/java/blackjack/domain/card/CardDeck.java @@ -1,46 +1,34 @@ package blackjack.domain.card; +import java.util.ArrayDeque; import java.util.ArrayList; -import java.util.Arrays; -import java.util.Collection; import java.util.Collections; import java.util.List; -import java.util.stream.Collectors; +import java.util.Queue; public class CardDeck { - private final List cards; + private final Queue cards; public CardDeck(List cards) { - this.cards = new ArrayList<>(cards); + this.cards = new ArrayDeque<>(cards); } - public CardDeck() { - this(initCards()); + public static CardDeck createGameDeck() { + List pool = Card.getPool(); + Collections.shuffle(pool); + return new CardDeck(pool); } - private static List initCards() { - List cards = Arrays.stream(Pattern.values()) - .map(CardDeck::createCardsBy) - .flatMap(Collection::stream) - .collect(Collectors.toList()); - Collections.shuffle(cards); - return cards; - } - - private static List createCardsBy(Pattern pattern) { - return Arrays.stream(Denomination.values()) - .map(denomination -> new Card(pattern, denomination)) - .collect(Collectors.toList()); + public Card draw() { + validateNotEmpty(); + return cards.poll(); } - public Card draw() { + private void validateNotEmpty() { if (cards.isEmpty()) { throw new IllegalStateException("[ERROR] 카드 덱이 비어 있습니다."); } - Card card = cards.get(0); - cards.remove(0); - return card; } public List drawDouble() { diff --git a/src/main/java/blackjack/domain/card/Cards.java b/src/main/java/blackjack/domain/card/Cards.java index 1aa583ff96..0640fae3cc 100644 --- a/src/main/java/blackjack/domain/card/Cards.java +++ b/src/main/java/blackjack/domain/card/Cards.java @@ -8,27 +8,16 @@ public class Cards { private static final int BUST_STANDARD = 21; - - private static final int INITIAL_CARDS_SIZE = 2; + private static final Score BLACKJACK_SCORE = new Score(21); + private static final int BLACKJACK_SIZE = 2; private final List cards; public Cards(List cards) { Objects.requireNonNull(cards, "[ERROR] 카드는 null일 수 없습니다."); cards = new ArrayList<>(cards); - validate(cards); - this.cards = cards; - } - - private void validate(List cards) { - validateSize(cards); validateDistinct(cards); - } - - private void validateSize(List cards) { - if (cards.size() != INITIAL_CARDS_SIZE) { - throw new IllegalArgumentException("[ERROR] 카드를 두 장 받고 시작해야 합니다."); - } + this.cards = cards; } private void validateDistinct(List cards) { @@ -37,8 +26,9 @@ private void validateDistinct(List cards) { } } - public void add(Card card) { + public Cards add(Card card) { cards.add(card); + return new Cards(cards); } public Score calculateScore() { @@ -60,6 +50,14 @@ private boolean containsAce(List cards) { .anyMatch(card -> card.isSameValueWith(Denomination.ACE)); } + public boolean isBust() { + return calculateScore().isGreaterThan(BUST_STANDARD); + } + + public boolean isBlackjack() { + return cards.size() == BLACKJACK_SIZE && calculateScore().equals(BLACKJACK_SCORE); + } + public List getCards() { return cards; } diff --git a/src/main/java/blackjack/domain/participant/Dealer.java b/src/main/java/blackjack/domain/participant/Dealer.java index 884eea327f..8c1d1b9065 100644 --- a/src/main/java/blackjack/domain/participant/Dealer.java +++ b/src/main/java/blackjack/domain/participant/Dealer.java @@ -1,8 +1,7 @@ package blackjack.domain.participant; -import blackjack.domain.Name; import blackjack.domain.card.Card; -import blackjack.domain.card.Cards; +import blackjack.domain.state.DealerRunning; import java.util.List; public class Dealer extends Participant { @@ -10,15 +9,11 @@ public class Dealer extends Participant { private static final int HIT_STANDARD = 17; public Dealer(List cards) { - super(new Name("딜러"), new Cards(cards)); + super(new Name("딜러"), DealerRunning.start(cards)); } @Override public List showInitialCards() { return List.of(getCards().get(0)); } - - public boolean shouldHit() { - return getScore() < HIT_STANDARD; - } } diff --git a/src/main/java/blackjack/domain/Name.java b/src/main/java/blackjack/domain/participant/Name.java similarity index 93% rename from src/main/java/blackjack/domain/Name.java rename to src/main/java/blackjack/domain/participant/Name.java index 160b2446fe..0ad2566cb3 100644 --- a/src/main/java/blackjack/domain/Name.java +++ b/src/main/java/blackjack/domain/participant/Name.java @@ -1,4 +1,4 @@ -package blackjack.domain; +package blackjack.domain.participant; import java.util.Objects; diff --git a/src/main/java/blackjack/domain/participant/Participant.java b/src/main/java/blackjack/domain/participant/Participant.java index 936b356d77..0c030aecc2 100644 --- a/src/main/java/blackjack/domain/participant/Participant.java +++ b/src/main/java/blackjack/domain/participant/Participant.java @@ -1,47 +1,55 @@ package blackjack.domain.participant; -import blackjack.domain.Name; -import blackjack.domain.State; +import blackjack.domain.Score; import blackjack.domain.card.Card; import blackjack.domain.card.CardDeck; -import blackjack.domain.card.Cards; +import blackjack.domain.state.Blackjack; +import blackjack.domain.state.Bust; +import blackjack.domain.state.State; import java.util.List; import java.util.Objects; public abstract class Participant { private final Name name; - private final Cards cards; + private State state; - protected Participant(Name name, Cards cards) { + protected Participant(Name name, State state) { Objects.requireNonNull(name, "[ERROR] 이름은 null일 수 없습니다."); - Objects.requireNonNull(cards, "[ERROR] 카드들은 null일 수 없습니다."); + Objects.requireNonNull(state, "[ERROR] 상태는 null일 수 없습니다."); this.name = name; - this.cards = cards; + this.state = state; } public void hit(CardDeck deck) { - cards.add(deck.draw()); + state = state.hit(deck.draw()); + } + + public void stand() { + state = state.stand(); + } + + public boolean isFinished() { + return state.isFinished(); } public boolean isBust() { - return State.from(cards) == State.BUST; + return state instanceof Bust; } - public boolean isBlackJack() { - return State.from(cards) == State.BLACKJACK; + public boolean isBlackjack() { + return state instanceof Blackjack; } - public int getScore() { - return cards.calculateScore() - .getValue(); + public Score getScore() { + return state.calculateScore(); } - abstract public List showInitialCards(); + public abstract List showInitialCards(); public List getCards() { - return List.copyOf(cards.getCards()); + return List.copyOf(state.getCards()); } public String getName() { diff --git a/src/main/java/blackjack/domain/participant/Player.java b/src/main/java/blackjack/domain/participant/Player.java index e6ce7cd6f8..ca54d938c1 100644 --- a/src/main/java/blackjack/domain/participant/Player.java +++ b/src/main/java/blackjack/domain/participant/Player.java @@ -1,16 +1,26 @@ package blackjack.domain.participant; -import blackjack.domain.Name; +import blackjack.domain.Betting; +import blackjack.domain.Outcome; import blackjack.domain.card.Card; -import blackjack.domain.card.Cards; +import blackjack.domain.card.CardDeck; +import blackjack.domain.state.PlayerRunning; +import blackjack.dto.HitRequest; import java.util.List; public class Player extends Participant { - private static final int HIT_STANDARD = 21; + private final Betting betting; - public Player(Name name, List cards) { - super(name, new Cards(cards)); + public Player(Name name, List cards, Betting betting) { + super(name, PlayerRunning.start(cards)); + this.betting = betting; + } + + public void proceed(CardDeck deck, HitRequest hitRequest) { + while (!isFinished() && hitRequest == HitRequest.YES) { + hit(deck); + } } @Override @@ -18,7 +28,7 @@ public List showInitialCards() { return List.copyOf(getCards()); } - public boolean isHittable() { - return getScore() < HIT_STANDARD; + public int calculateProfit(Outcome outcome) { + return (int) (betting.getBetMoney() * outcome.getProfitRate()); } } diff --git a/src/main/java/blackjack/domain/participant/PlayerNames.java b/src/main/java/blackjack/domain/participant/PlayerNames.java new file mode 100644 index 0000000000..58cdda5f7b --- /dev/null +++ b/src/main/java/blackjack/domain/participant/PlayerNames.java @@ -0,0 +1,45 @@ +package blackjack.domain.participant; + +import java.util.List; +import java.util.stream.Collectors; + +public class PlayerNames { + + private static final int PLAYER_MIN_SIZE = 2; + private static final int PLAYER_MAX_SIZE = 8; + + private final List names; + + public PlayerNames(List nameStrings) { + nameStrings = List.copyOf(nameStrings); + validate(nameStrings); + names = createNames(nameStrings); + } + + private void validate(List nameStrings) { + validateSize(nameStrings); + validateNotDuplicated(nameStrings); + } + + private void validateSize(List nameStrings) { + if (nameStrings.size() < PLAYER_MIN_SIZE || nameStrings.size() > PLAYER_MAX_SIZE) { + throw new IllegalArgumentException("[ERROR] 2~8인의 플레이어가 참가할 수 있습니다."); + } + } + + private void validateNotDuplicated(List nameStrings) { + if (nameStrings.stream().distinct().count() != nameStrings.size()) { + throw new IllegalArgumentException("[ERROR] 플레이어 이름은 중복될 수 없습니다."); + } + } + + private List createNames(List nameStrings) { + return nameStrings.stream() + .map(Name::new) + .collect(Collectors.toList()); + } + + public List getNames() { + return names; + } +} 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 0000000000..6289a720ad --- /dev/null +++ b/src/main/java/blackjack/domain/participant/Players.java @@ -0,0 +1,51 @@ +package blackjack.domain.participant; + +import blackjack.domain.Betting; +import blackjack.domain.card.CardDeck; +import java.util.Iterator; +import java.util.List; +import java.util.function.Function; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +public class Players implements Iterable { + + private final List players; + + public Players(List players) { + this.players = players; + } + + public Players(PlayerNames names, CardDeck deck, Function inputBetting) { + this(createPlayers(names, deck, inputBetting)); + } + + public static List createPlayers(PlayerNames names, CardDeck deck, + Function inputBetting) { + return names.getNames() + .stream() + .map(name -> new Player(name, deck.drawDouble(), inputBetting.apply(name))) + .collect(Collectors.toUnmodifiableList()); + } + + @Override + public Iterator iterator() { + return new Iterator<>() { + int current = 0; + + @Override + public boolean hasNext() { + return current < players.size(); + } + + @Override + public Player next() { + return players.get(current++); + } + }; + } + + public Stream stream() { + return players.stream(); + } +} diff --git a/src/main/java/blackjack/domain/state/AbstractState.java b/src/main/java/blackjack/domain/state/AbstractState.java new file mode 100644 index 0000000000..c8e88f163c --- /dev/null +++ b/src/main/java/blackjack/domain/state/AbstractState.java @@ -0,0 +1,27 @@ +package blackjack.domain.state; + +import blackjack.domain.Score; +import blackjack.domain.card.Card; +import blackjack.domain.card.Cards; +import java.util.List; +import java.util.Objects; + +public abstract class AbstractState implements State { + + protected final Cards cards; + + protected AbstractState(Cards cards) { + Objects.requireNonNull(cards, "[ERROR] 카드패는 null일 수 없습니다."); + this.cards = cards; + } + + @Override + public List getCards() { + return cards.getCards(); + } + + @Override + public Score calculateScore() { + return cards.calculateScore(); + } +} diff --git a/src/main/java/blackjack/domain/state/Blackjack.java b/src/main/java/blackjack/domain/state/Blackjack.java new file mode 100644 index 0000000000..bce9379a47 --- /dev/null +++ b/src/main/java/blackjack/domain/state/Blackjack.java @@ -0,0 +1,10 @@ +package blackjack.domain.state; + +import blackjack.domain.card.Cards; + +public final class Blackjack extends Finished { + + Blackjack(Cards cards) { + super(cards); + } +} diff --git a/src/main/java/blackjack/domain/state/Bust.java b/src/main/java/blackjack/domain/state/Bust.java new file mode 100644 index 0000000000..2cb5ec54e5 --- /dev/null +++ b/src/main/java/blackjack/domain/state/Bust.java @@ -0,0 +1,10 @@ +package blackjack.domain.state; + +import blackjack.domain.card.Cards; + +public final class Bust extends Finished { + + Bust(Cards cards) { + super(cards); + } +} diff --git a/src/main/java/blackjack/domain/state/DealerRunning.java b/src/main/java/blackjack/domain/state/DealerRunning.java new file mode 100644 index 0000000000..d51d6cd68e --- /dev/null +++ b/src/main/java/blackjack/domain/state/DealerRunning.java @@ -0,0 +1,46 @@ +package blackjack.domain.state; + +import blackjack.domain.card.Card; +import blackjack.domain.card.Cards; +import java.util.List; + +public final class DealerRunning extends Running { + + private static final int HIT_STANDARD_SCORE = 17; + + DealerRunning(Cards cards) { + super(cards); + } + + public static State start(List initialCards) { + Cards cards = new Cards(initialCards); + if (cards.isBlackjack()) { + return new Blackjack(cards); + } + if (shouldHit(cards)) { + return new DealerRunning(cards); + } + return new Stand(cards); + } + + @Override + public State hit(Card card) { + Cards cards = this.cards.add(card); + if (cards.isBust()) { + return new Bust(cards); + } + if (shouldHit(cards)) { + return new DealerRunning(cards); + } + return new Stand(cards); + } + + private static boolean shouldHit(Cards cards) { + return cards.calculateScore().isLessThan(HIT_STANDARD_SCORE); + } + + @Override + public State stand() { + throw new UnsupportedOperationException("[ERROR] 딜러는 스스로 Stand할 수 없습니다."); + } +} diff --git a/src/main/java/blackjack/domain/state/Finished.java b/src/main/java/blackjack/domain/state/Finished.java new file mode 100644 index 0000000000..ea7c807bff --- /dev/null +++ b/src/main/java/blackjack/domain/state/Finished.java @@ -0,0 +1,26 @@ +package blackjack.domain.state; + +import blackjack.domain.card.Card; +import blackjack.domain.card.Cards; + +public abstract class Finished extends AbstractState { + + protected Finished(Cards cards) { + super(cards); + } + + @Override + public State hit(Card card) { + throw new UnsupportedOperationException("[ERROR] 게임을 진행할 수 없습니다."); + } + + @Override + public State stand() { + throw new UnsupportedOperationException("[ERROR] 게임을 진행할 수 없습니다."); + } + + @Override + public boolean isFinished() { + return true; + } +} diff --git a/src/main/java/blackjack/domain/state/PlayerRunning.java b/src/main/java/blackjack/domain/state/PlayerRunning.java new file mode 100644 index 0000000000..ed6560dd54 --- /dev/null +++ b/src/main/java/blackjack/domain/state/PlayerRunning.java @@ -0,0 +1,34 @@ +package blackjack.domain.state; + +import blackjack.domain.card.Card; +import blackjack.domain.card.Cards; +import java.util.List; + +public final class PlayerRunning extends Running { + + PlayerRunning(Cards cards) { + super(cards); + } + + public static State start(List initialCards) { + Cards cards = new Cards(initialCards); + if (cards.isBlackjack()) { + return new Blackjack(cards); + } + return new PlayerRunning(cards); + } + + @Override + public State hit(Card card) { + Cards cards = this.cards.add(card); + if (cards.isBust()) { + return new Bust(cards); + } + return new PlayerRunning(cards); + } + + @Override + public State stand() { + return new Stand(cards); + } +} diff --git a/src/main/java/blackjack/domain/state/Running.java b/src/main/java/blackjack/domain/state/Running.java new file mode 100644 index 0000000000..022049871d --- /dev/null +++ b/src/main/java/blackjack/domain/state/Running.java @@ -0,0 +1,15 @@ +package blackjack.domain.state; + +import blackjack.domain.card.Cards; + +public abstract class Running extends AbstractState { + + protected Running(Cards cards) { + super(cards); + } + + @Override + public boolean isFinished() { + return false; + } +} diff --git a/src/main/java/blackjack/domain/state/Stand.java b/src/main/java/blackjack/domain/state/Stand.java new file mode 100644 index 0000000000..7530a94cf3 --- /dev/null +++ b/src/main/java/blackjack/domain/state/Stand.java @@ -0,0 +1,10 @@ +package blackjack.domain.state; + +import blackjack.domain.card.Cards; + +public final class Stand extends Finished { + + Stand(Cards cards) { + super(cards); + } +} diff --git a/src/main/java/blackjack/domain/state/State.java b/src/main/java/blackjack/domain/state/State.java new file mode 100644 index 0000000000..ddc069bc8a --- /dev/null +++ b/src/main/java/blackjack/domain/state/State.java @@ -0,0 +1,18 @@ +package blackjack.domain.state; + +import blackjack.domain.Score; +import blackjack.domain.card.Card; +import java.util.List; + +public interface State { + + State hit(Card card); + + State stand(); + + boolean isFinished(); + + List getCards(); + + Score calculateScore(); +} diff --git a/src/main/java/blackjack/dto/ParticipantResponse.java b/src/main/java/blackjack/dto/ParticipantResponse.java index e72fcbdc9d..c638d2e7ca 100644 --- a/src/main/java/blackjack/dto/ParticipantResponse.java +++ b/src/main/java/blackjack/dto/ParticipantResponse.java @@ -13,7 +13,7 @@ public class ParticipantResponse { public ParticipantResponse(Participant participant) { this.name = participant.getName(); this.cards = List.copyOf(participant.getCards()); - this.score = participant.getScore(); + this.score = participant.getScore().getValue(); } public String getName() { diff --git a/src/main/java/blackjack/view/InputView.java b/src/main/java/blackjack/view/InputView.java index 4e52f8da3e..004c8ec234 100644 --- a/src/main/java/blackjack/view/InputView.java +++ b/src/main/java/blackjack/view/InputView.java @@ -1,10 +1,15 @@ package blackjack.view; +import blackjack.dto.HitRequest; import java.util.List; import java.util.Scanner; public class InputView { + private InputView() { + + } + private static final Scanner scanner = new Scanner(System.in); public static List inputPlayerNames() { @@ -13,9 +18,18 @@ public static List inputPlayerNames() { .split(",", -1)); } - public static String inputHitRequest(String playerName) { - System.out.printf("%s는(은) 한 장의 카드를 더 받으시겠습니까? (예는 y, 아니오는 n)", playerName); - System.out.println(); - return scanner.nextLine().toLowerCase(); + public static HitRequest inputHitRequest(String playerName) { + System.out.printf("%n%s는(은) 한 장의 카드를 더 받으시겠습니까? (예는 y, 아니오는 n)%n", playerName); + return HitRequest.find(scanner.nextLine()); + } + + public static int inputBetMoney(String name) { + try { + System.out.printf("%s의 베팅 금액은?%n", name); + return Integer.parseInt(scanner.nextLine()); + } catch (NumberFormatException e) { + System.out.println("[ERROR] 베팅 금액은 숫자여야 합니다."); + return inputBetMoney(name); + } } } diff --git a/src/main/java/blackjack/view/OutputView.java b/src/main/java/blackjack/view/OutputView.java index a450ecff79..d118a3560e 100644 --- a/src/main/java/blackjack/view/OutputView.java +++ b/src/main/java/blackjack/view/OutputView.java @@ -1,7 +1,6 @@ package blackjack.view; import blackjack.domain.GameResult; -import blackjack.domain.Outcome; import blackjack.domain.card.Card; import blackjack.dto.ParticipantInitialResponse; import blackjack.dto.ParticipantResponse; @@ -11,6 +10,10 @@ public class OutputView { + private OutputView() { + + } + public static void printErrorMessage(String message) { System.out.println(message); } @@ -19,17 +22,21 @@ public static void printStartMessage(ParticipantInitialResponse dealer, List playerNames = players.stream() .map(ParticipantResponse::getName) .collect(Collectors.toUnmodifiableList()); - System.out.printf("%s와 %s에게 2장의 카드를 나누었습니다.%n", dealer.getName(), String.join(", ", playerNames)); + System.out.printf("%n%s와 %s에게 2장의 카드를 나누었습니다.%n%n", dealer.getName(), String.join(", ", playerNames)); printInitialDealerCards(dealer); - players.forEach(OutputView::printParticipantCards); + players.forEach(OutputView::printPlayerCards); } - public static void printInitialDealerCards(ParticipantInitialResponse participant) { - String cardsInfo = createCardsString(participant.getCards()); - System.out.printf("%s 카드: %s%n", participant.getName(), cardsInfo); + public static void printInitialDealerCards(ParticipantInitialResponse dealer) { + String cardsInfo = createCardsString(dealer.getCards()); + System.out.printf("%s 카드: %s%n", dealer.getName(), cardsInfo); + } + + public static void printPlayersCards(List players) { + players.forEach(OutputView::printPlayerCards); } - public static void printParticipantCards(ParticipantResponse participant) { + public static void printPlayerCards(ParticipantResponse participant) { String cardsInfo = createCardsString(participant.getCards()); System.out.printf("%s 카드: %s - 합계: %d%n", participant.getName(), cardsInfo, participant.getScore()); } @@ -60,30 +67,17 @@ public static void printCardResultMessage() { System.out.printf("%n## 최종 카드%n"); } - public static void printWinResult(GameResult gameResult) { - System.out.printf("%n## 최종 승패%n"); - printDealerWinResult(gameResult.getDealerResult()); - printPlayersWinResult(gameResult.getPlayersResult()); - } - - private static void printDealerWinResult(Map dealerResult) { - System.out.printf("딜러: %s%n", createDealerWinResultString(dealerResult)); - } - - private static String createDealerWinResultString(Map dealerResult) { - return dealerResult.keySet() - .stream() - .filter(outcome -> dealerResult.get(outcome) > 0) - .map(outcome -> String.format("%d%s", dealerResult.get(outcome), outcome.getName())) - .collect(Collectors.joining(" ")); + public static void printGameResult(GameResult gameResult) { + System.out.printf("%n## 최종 수익%n"); + printDealerProfit(gameResult); + printPlayersProfit(gameResult.getProfits()); } - private static void printPlayersWinResult(Map playersResult) { - playersResult.keySet() - .forEach(playerName -> printPlayerWinResult(playerName, playersResult.get(playerName))); + private static void printDealerProfit(GameResult gameResult) { + System.out.printf("딜러: %d%n", gameResult.getDealerProfit()); } - private static void printPlayerWinResult(String playerName, Outcome outcome) { - System.out.printf("%s: %s%n", playerName, outcome.getName()); + private static void printPlayersProfit(Map playersProfits) { + playersProfits.forEach((key, value) -> System.out.printf("%s: %d%n", key, value)); } } diff --git a/src/test/java/blackjack/domain/BettingTest.java b/src/test/java/blackjack/domain/BettingTest.java new file mode 100644 index 0000000000..4cb050f38f --- /dev/null +++ b/src/test/java/blackjack/domain/BettingTest.java @@ -0,0 +1,16 @@ +package blackjack.domain; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class BettingTest { + + @Test + @DisplayName("베팅 금액은 0원 이하면 예외를 반환한다.") + void lessThanOrEqualsToZero() { + assertThatThrownBy(() -> new Betting(0)).isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 베팅 금액은 0원 이하일 수 없습니다."); + } +} diff --git a/src/test/java/blackjack/domain/GameResultTest.java b/src/test/java/blackjack/domain/GameResultTest.java index 761ea4fedb..18ecbbe223 100644 --- a/src/test/java/blackjack/domain/GameResultTest.java +++ b/src/test/java/blackjack/domain/GameResultTest.java @@ -1,75 +1,55 @@ package blackjack.domain; -import static blackjack.domain.Outcome.DRAW; -import static blackjack.domain.Outcome.LOSE; -import static blackjack.domain.Outcome.WIN; -import static blackjack.domain.card.Denomination.ACE; -import static blackjack.domain.card.Denomination.NINE; -import static blackjack.domain.card.Denomination.TEN; -import static blackjack.domain.card.Pattern.CLOVER; -import static blackjack.domain.card.Pattern.DIAMOND; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.junit.jupiter.api.Assertions.assertAll; import blackjack.domain.card.Card; -import blackjack.domain.card.Denomination; import blackjack.domain.participant.Dealer; +import blackjack.domain.participant.Name; import blackjack.domain.participant.Player; -import java.util.EnumMap; +import blackjack.domain.participant.Players; +import blackjack.util.BlackjackTestUtil; import java.util.List; import java.util.Map; 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; public class GameResultTest { - @Test - @DisplayName("승패 결과를 Map에 저장한다") - void saveIntoMap() { + @ParameterizedTest + @CsvSource(value = {"21,1500", "20,1000", "19,0", "18,-1000"}) + @DisplayName("수익을 Map에 저장한다") + void saveIntoMap(int playerScore, int expected) { // given - Dealer dealer = createDealer(TEN); - - Player winPlayer = createPlayer("win", ACE); - Player drawPlayer = createPlayer("draw", TEN); - Player losePlayer = createPlayer("lose", NINE); - List players = List.of(winPlayer, drawPlayer, losePlayer); + Dealer dealer = BlackjackTestUtil.createDealer(19); - Map outcomeMap = createOutcomeMap(1, 1, 1); + Player player = BlackjackTestUtil.createPlayer(playerScore); + Players players = new Players(List.of(player)); // when GameResult gameResult = GameResult.of(dealer, players); - Map dealerResult = gameResult.getDealerResult(); - Map playersResult = gameResult.getPlayersResult(); + Map profits = gameResult.getProfits(); // then - assertAll( - () -> assertThat(dealerResult).isEqualTo(outcomeMap), - () -> assertThat(playersResult.get(winPlayer.getName())).isEqualTo(WIN), - () -> assertThat(playersResult.get(drawPlayer.getName())).isEqualTo(DRAW), - () -> assertThat(playersResult.get(losePlayer.getName())).isEqualTo(LOSE) - ); + assertThat(profits.get(player.getName())).isEqualTo(expected); } - private static Dealer createDealer(Denomination denomination2) { - Card card1 = new Card(DIAMOND, TEN); - Card card2 = new Card(CLOVER, denomination2); - List dealerCards = List.of(card1, card2); - return new Dealer(dealerCards); - } + @Test + @DisplayName("딜러의 수익은 모든 플레이어 수익의 합의 반대다.") + void dealerProfit() { + // given + Dealer dealer = BlackjackTestUtil.createDealer(19); - private static Player createPlayer(String name, Denomination denomination2) { - Card card1 = new Card(DIAMOND, TEN); - Card card2 = new Card(CLOVER, denomination2); - List playerCards = List.of(card1, card2); - return new Player(new Name(name), playerCards); - } + List cards = BlackjackTestUtil.createCards(20); + Player player1 = new Player(new Name("player1"), cards, new Betting(1000)); + Player player2 = new Player(new Name("player2"), cards, new Betting(1000)); + Players players = new Players(List.of(player1, player2)); - private static Map createOutcomeMap(int win, int draw, int lose) { - Map judgementMap = new EnumMap<>(Outcome.class); - judgementMap.put(WIN, win); - judgementMap.put(DRAW, draw); - judgementMap.put(LOSE, lose); + // when + GameResult gameResult = GameResult.of(dealer, players); - return judgementMap; + // then + assertThat(gameResult.getDealerProfit()).isEqualTo(-2000); } } diff --git a/src/test/java/blackjack/domain/OutcomeTest.java b/src/test/java/blackjack/domain/OutcomeTest.java index 940110d598..e23605c0a9 100644 --- a/src/test/java/blackjack/domain/OutcomeTest.java +++ b/src/test/java/blackjack/domain/OutcomeTest.java @@ -3,66 +3,50 @@ import static blackjack.domain.Outcome.DRAW; import static blackjack.domain.Outcome.LOSE; import static blackjack.domain.Outcome.WIN; -import static blackjack.domain.card.Denomination.ACE; -import static blackjack.domain.card.Denomination.EIGHT; -import static blackjack.domain.card.Denomination.FIVE; -import static blackjack.domain.card.Denomination.NINE; -import static blackjack.domain.card.Denomination.SIX; -import static blackjack.domain.card.Denomination.TEN; -import static blackjack.domain.card.Denomination.TWO; -import static blackjack.domain.card.Pattern.CLOVER; -import static blackjack.domain.card.Pattern.DIAMOND; -import static blackjack.domain.card.Pattern.HEART; +import static blackjack.domain.Outcome.WIN_BLACKJACK; +import static blackjack.util.BlackjackTestUtil.createDealer; +import static blackjack.util.BlackjackTestUtil.createDeck; +import static blackjack.util.BlackjackTestUtil.createPlayer; import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import blackjack.domain.card.Card; import blackjack.domain.card.CardDeck; -import blackjack.domain.card.Denomination; import blackjack.domain.participant.Dealer; import blackjack.domain.participant.Player; -import java.util.List; -import java.util.stream.Stream; 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.CsvSource; -import org.junit.jupiter.params.provider.MethodSource; +import org.junit.jupiter.params.provider.ValueSource; public class OutcomeTest { @ParameterizedTest - @MethodSource("provideForNoOneBust") + @CsvSource(value = {"20, WIN", "19, DRAW", "18, LOSE"}) @DisplayName("딜러와 플레이어 둘 다 버스트하지 않았을 경우 점수가 더 큰 쪽이 이긴다.") - void bothNotBust(Dealer dealer, Outcome playerOutcome) { + void bothNotBust(int playerScore, String outcomeName) { // given - Player player = createPlayer(NINE); + Dealer dealer = createDealer(19); + Player player = createPlayer(playerScore); + Outcome expected = Outcome.valueOf(outcomeName); // when Outcome actual = Outcome.judge(player, dealer); // then - assertThat(actual).isEqualTo(playerOutcome); - } - - private static Stream provideForNoOneBust() { - return Stream.of( - Arguments.of(createDealer(TEN), LOSE), - Arguments.of(createDealer(NINE), DRAW), - Arguments.of(createDealer(EIGHT), WIN) - ); + assertThat(actual).isEqualTo(expected); } @ParameterizedTest - @MethodSource("provideForPlayerBust") + @ValueSource(ints = {4, 5, 6}) @DisplayName("플레이어가 버스트면 무조건 딜러가 이긴다") - void playerBust(CardDeck deck) { + void playerBust(int additionalCardScore) { // given - Dealer dealer = createDealer(SIX); - dealer.hit(deck); - - Player player = createPlayer(TEN); - player.hit(deck); + Player player = createPlayer(12); + Dealer dealer = createDealer(6); + CardDeck deckForPlayer = createDeck(10); + CardDeck deckForDealer = createDeck(additionalCardScore); + player.hit(deckForPlayer); + dealer.hit(deckForDealer); // when Outcome actual = Outcome.judge(player, dealer); @@ -71,21 +55,14 @@ void playerBust(CardDeck deck) { assertThat(actual).isEqualTo(LOSE); } - private static Stream provideForPlayerBust() { - return Stream.of( - Arguments.of(new CardDeck(List.of(new Card(HEART, SIX), new Card(HEART, TWO)))), - Arguments.of(new CardDeck(List.of(new Card(HEART, FIVE), new Card(HEART, TWO)))) - ); - } - @Test @DisplayName("플레이어가 버스트가 아니고 딜러가 버스트면 플레이어가 이긴다") void dealerBust() { // given - Dealer dealer = createDealer(SIX); - dealer.hit(new CardDeck(List.of(new Card(HEART, SIX)))); + Player player = createPlayer(20); - Player player = createPlayer(TEN); + Dealer dealer = createDealer(16); + dealer.hit(createDeck(6)); // when Outcome actual = Outcome.judge(player, dealer); @@ -98,25 +75,25 @@ void dealerBust() { @DisplayName("똑같이 21점이어도 블랙잭이 이긴다.") void blackJackDoesNotDefeat() { // given - Dealer dealer = createDealer(ACE); + Dealer dealer = createDealer(16); - Player player = createPlayer(TEN); - player.hit(new CardDeck(List.of(new Card(HEART, ACE)))); + Player player = createPlayer(21); + dealer.hit(createDeck(5)); // when Outcome actual = Outcome.judge(player, dealer); // then - assertThat(actual).isEqualTo(LOSE); + assertThat(actual).isEqualTo(WIN_BLACKJACK); } @Test @DisplayName("블랙잭 끼리는 비긴다") void blackJackDrawWithBlackJack() { // given - Dealer dealer = createDealer(ACE); + Dealer dealer = createDealer(21); - Player player = createPlayer(ACE); + Player player = createPlayer(21); // when Outcome actual = Outcome.judge(player, dealer); @@ -126,31 +103,16 @@ void blackJackDrawWithBlackJack() { } @ParameterizedTest - @CsvSource(value = {"WIN,LOSE", "DRAW,DRAW", "LOSE,WIN"}) - @DisplayName("승무패의 반대를 반환한다") - void returnOpposite(String inputName, String expectedName) { + @CsvSource(value = {"WIN_BLACKJACK,1.5", "WIN,1", "DRAW,0", "LOSE,-1"}) + @DisplayName("각각의 결과값은 수익률을 가지고 있다") + void returnOpposite(String inputName, double expected) { // given Outcome outcome = Outcome.valueOf(inputName); - Outcome expected = Outcome.valueOf(expectedName); // when - Outcome actual = outcome.getOpposite(); + double actual = outcome.getProfitRate(); // then assertThat(actual).isEqualTo(expected); } - - private static Dealer createDealer(Denomination denomination2) { - Card card1 = new Card(DIAMOND, TEN); - Card card2 = new Card(CLOVER, denomination2); - List dealerCards = List.of(card1, card2); - return new Dealer(dealerCards); - } - - private static Player createPlayer(Denomination denomination2) { - Card card1 = new Card(DIAMOND, TEN); - Card card2 = new Card(CLOVER, denomination2); - List playerCards = List.of(card1, card2); - return new Player(new Name("player"), playerCards); - } } diff --git a/src/test/java/blackjack/domain/ScoreTest.java b/src/test/java/blackjack/domain/ScoreTest.java new file mode 100644 index 0000000000..13b1f3c42e --- /dev/null +++ b/src/test/java/blackjack/domain/ScoreTest.java @@ -0,0 +1,48 @@ +package blackjack.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class ScoreTest { + + @Test + @DisplayName("인자로 들어온 값보다 작으면 isLessThan은 true를 반환한다.") + void isLessThan() { + //given + Score score = new Score(0); + + //when + boolean actual = score.isLessThan(1); + + //then + assertThat(actual).isTrue(); + } + + @Test + @DisplayName("인자로 들어온 값보다 크면 isGreaterThan은 true를 반환한다.") + void isGreaterThan() { + //given + Score score = new Score(1); + + //when + boolean actual = score.isGreaterThan(0); + + //then + assertThat(actual).isTrue(); + } + + @Test + @DisplayName("isGreaterThan은 Score와도 비교가 가능하다.") + void isLessThanScore() { + //given + Score score = new Score(1); + + //when + boolean actual = score.isGreaterThan(new Score(0)); + + //then + assertThat(actual).isTrue(); + } +} diff --git a/src/test/java/blackjack/domain/StateTest.java b/src/test/java/blackjack/domain/StateTest.java deleted file mode 100644 index 43810daf29..0000000000 --- a/src/test/java/blackjack/domain/StateTest.java +++ /dev/null @@ -1,92 +0,0 @@ -package blackjack.domain; - -import static org.assertj.core.api.Assertions.assertThat; - -import blackjack.domain.card.Card; -import blackjack.domain.card.Cards; -import blackjack.domain.card.Denomination; -import blackjack.domain.card.Pattern; -import java.util.List; -import java.util.stream.Stream; -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; - -public class StateTest { - - @ParameterizedTest - @MethodSource("provideBustTest") - @DisplayName("카드의 총합이 21이 넘는 경우 버스트") - void bust(Cards cards, boolean expected) { - // when - State actual = State.from(cards); - - // then - assertThat(actual == State.BUST).isEqualTo(expected); - } - - private static Stream provideBustTest() { - Card DIAMOND_TEN = new Card(Pattern.DIAMOND, Denomination.TEN); - Card CLOVER_TEN = new Card(Pattern.CLOVER, Denomination.TEN); - Card HEART_TWO = new Card(Pattern.HEART, Denomination.TWO); - Card SPADE_ACE = new Card(Pattern.SPADE, Denomination.ACE); - - Cards bustCards = new Cards(List.of(DIAMOND_TEN, CLOVER_TEN)); - Cards notBustCards = new Cards(List.of(DIAMOND_TEN, CLOVER_TEN)); - - bustCards.add(HEART_TWO); - notBustCards.add(SPADE_ACE); - - return Stream.of( - Arguments.of(bustCards, true), - Arguments.of(notBustCards, false) - ); - } - - @ParameterizedTest - @MethodSource("provideBlackJackTest") - @DisplayName("점수의 총합이 21이면서 2장이면 블랙잭이다.") - void blackJack(Cards cards, boolean expected) { - // when - State actual = State.from(cards); - - // then - assertThat(actual == State.BLACKJACK).isEqualTo(expected); - } - - private static Stream provideBlackJackTest() { - Card DIAMOND_TEN = new Card(Pattern.DIAMOND, Denomination.TEN); - Card CLOVER_TEN = new Card(Pattern.CLOVER, Denomination.TEN); - Card SPADE_ACE = new Card(Pattern.SPADE, Denomination.ACE); - - Cards blackJackCards = new Cards(List.of(DIAMOND_TEN, SPADE_ACE)); - Cards notBlackJackCards = new Cards(List.of(DIAMOND_TEN, SPADE_ACE)); - - notBlackJackCards.add(CLOVER_TEN); - - return Stream.of( - Arguments.of(blackJackCards, true), - Arguments.of(notBlackJackCards, false) - ); - } - - @Test - @DisplayName("버스트도 블랙잭도 아니면 NOTHING을 반환한다") - void nothing() { - // given - Card DIAMOND_TEN = new Card(Pattern.DIAMOND, Denomination.TEN); - Card CLOVER_TEN = new Card(Pattern.CLOVER, Denomination.TEN); - Card HEART_ACE = new Card(Pattern.HEART, Denomination.ACE); - - Cards cards = new Cards(List.of(DIAMOND_TEN, CLOVER_TEN)); - cards.add(HEART_ACE); - - // when - State actual = State.from(cards); - - // then - assertThat(actual).isEqualTo(State.NOTHING); - } -} diff --git a/src/test/java/blackjack/domain/card/CardDeckTest.java b/src/test/java/blackjack/domain/card/CardDeckTest.java index 7038c27a26..da6b9d0d82 100644 --- a/src/test/java/blackjack/domain/card/CardDeckTest.java +++ b/src/test/java/blackjack/domain/card/CardDeckTest.java @@ -11,25 +11,25 @@ public class CardDeckTest { @Test - @DisplayName("카드 뭉치에서 카드를 한 장 뽑아서 준다.") + @DisplayName("카드 뭉치의 맨 위의 카드 한 장을 뽑아서 준다.") void drawCard() { // given - Card card = new Card(Pattern.DIAMOND, Denomination.TWO); - List cards = List.of(card); - CardDeck deck = new CardDeck(cards); + Card firstCard = Card.of(Pattern.DIAMOND, Denomination.ACE); + Card secondCard = Card.of(Pattern.DIAMOND, Denomination.TWO); + CardDeck deck = new CardDeck(List.of(firstCard, secondCard)); // when Card actual = deck.draw(); // then - assertThat(actual).isEqualTo(card); + assertThat(actual).isEqualTo(firstCard).isNotEqualTo(secondCard); } @Test @DisplayName("두 장의 카드를 한번에 뽑을 수 있다.") void drawDouble() { // given - CardDeck deck = new CardDeck(); + CardDeck deck = CardDeck.createGameDeck(); // when List actual = deck.drawDouble(); diff --git a/src/test/java/blackjack/domain/card/CardTest.java b/src/test/java/blackjack/domain/card/CardTest.java index 5c28ea25bc..55d450456d 100644 --- a/src/test/java/blackjack/domain/card/CardTest.java +++ b/src/test/java/blackjack/domain/card/CardTest.java @@ -11,7 +11,7 @@ public class CardTest { @DisplayName("카드를 생성한다.") void createCard() { // when - Card card = new Card(Pattern.DIAMOND, Denomination.ACE); + Card card = Card.of(Pattern.DIAMOND, Denomination.ACE); // then assertThat(card).isNotNull(); diff --git a/src/test/java/blackjack/domain/card/CardsTest.java b/src/test/java/blackjack/domain/card/CardsTest.java index 36d7eeadaa..94aa555605 100644 --- a/src/test/java/blackjack/domain/card/CardsTest.java +++ b/src/test/java/blackjack/domain/card/CardsTest.java @@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import blackjack.domain.Score; +import blackjack.util.BlackjackTestUtil; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -23,8 +24,8 @@ void notNull() { @DisplayName("시작 시 카드를 두 장 가지고 있어야 한다.") void validCardsSize() { // given - Card card1 = new Card(Pattern.DIAMOND, Denomination.THREE); - Card card2 = new Card(Pattern.CLOVER, Denomination.THREE); + Card card1 = Card.of(Pattern.DIAMOND, Denomination.THREE); + Card card2 = Card.of(Pattern.CLOVER, Denomination.THREE); List cards = List.of(card1, card2); // when @@ -34,26 +35,11 @@ void validCardsSize() { assertThat(actual.getCards()).containsOnly(card1, card2); } - @Test - @DisplayName("시작 시 카드가 두 장이 아니면 예외가 발생한다.") - void invalidCardsSize() { - // given - Card card1 = new Card(Pattern.DIAMOND, Denomination.THREE); - Card card2 = new Card(Pattern.CLOVER, Denomination.THREE); - Card card3 = new Card(Pattern.HEART, Denomination.THREE); - List cards = List.of(card1, card2, card3); - - // then - assertThatThrownBy(() -> new Cards(cards)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessage("[ERROR] 카드를 두 장 받고 시작해야 합니다."); - } - @Test @DisplayName("카드가 중복되면 예외가 발생한다.") void duplicatedCards() { // given - Card card = new Card(Pattern.DIAMOND, Denomination.THREE); + Card card = Card.of(Pattern.DIAMOND, Denomination.THREE); List cards = List.of(card, card); // then @@ -66,8 +52,8 @@ void duplicatedCards() { @DisplayName("카드의 점수 총합을 계산한다. (에이스가 없는 경우)") void calculateCardsSum() { // given - Card card1 = new Card(Pattern.DIAMOND, Denomination.THREE); - Card card2 = new Card(Pattern.CLOVER, Denomination.THREE); + Card card1 = Card.of(Pattern.DIAMOND, Denomination.THREE); + Card card2 = Card.of(Pattern.CLOVER, Denomination.THREE); Cards cards = new Cards(List.of(card1, card2)); // when @@ -82,8 +68,8 @@ void calculateCardsSum() { @DisplayName("카드의 점수 총합을 계산한다. (에이스가 있는 경우)") void calculateCardsSumWithACE() { // given - Card card1 = new Card(Pattern.DIAMOND, Denomination.TEN); - Card card2 = new Card(Pattern.CLOVER, Denomination.ACE); + Card card1 = Card.of(Pattern.DIAMOND, Denomination.TEN); + Card card2 = Card.of(Pattern.CLOVER, Denomination.ACE); Cards cards = new Cards(List.of(card1, card2)); // when @@ -98,11 +84,9 @@ void calculateCardsSumWithACE() { @DisplayName("카드의 점수 총합을 계산한다. (총 합이 21이 넘는 경우)") void calculateCardsSumOver21() { // given - Card card1 = new Card(Pattern.DIAMOND, Denomination.TEN); - Card card2 = new Card(Pattern.CLOVER, Denomination.TEN); - Card card3 = new Card(Pattern.HEART, Denomination.TWO); - Cards cards = new Cards(List.of(card1, card2)); - cards.add(card3); + Card additionalCard = Card.of(Pattern.HEART, Denomination.TWO); + Cards cards = new Cards(BlackjackTestUtil.createCards(20)); + cards.add(additionalCard); // when Score actual = cards.calculateScore(); diff --git a/src/test/java/blackjack/domain/participant/DealerTest.java b/src/test/java/blackjack/domain/participant/DealerTest.java index 7aba1e29d6..ce35849621 100644 --- a/src/test/java/blackjack/domain/participant/DealerTest.java +++ b/src/test/java/blackjack/domain/participant/DealerTest.java @@ -3,7 +3,6 @@ import static org.assertj.core.api.Assertions.assertThat; import blackjack.domain.card.Card; -import blackjack.domain.card.CardDeck; import blackjack.domain.card.Denomination; import blackjack.domain.card.Pattern; import java.util.List; @@ -16,8 +15,8 @@ public class DealerTest { @DisplayName("딜러는 처음에 맨 앞 한 장만 보여준다.") void showOnlyOneCard() { // given - Card card1 = new Card(Pattern.DIAMOND, Denomination.THREE); - Card card2 = new Card(Pattern.CLOVER, Denomination.THREE); + Card card1 = Card.of(Pattern.DIAMOND, Denomination.THREE); + Card card2 = Card.of(Pattern.CLOVER, Denomination.THREE); List cards = List.of(card1, card2); Dealer dealer = new Dealer(cards); @@ -28,57 +27,4 @@ void showOnlyOneCard() { // then assertThat(actual).containsOnly(card1); } - - @Test - @DisplayName("딜러가 카드 한 장을 더 받는 경우") - void addCard() { - // given - Card card1 = new Card(Pattern.DIAMOND, Denomination.THREE); - Card card2 = new Card(Pattern.CLOVER, Denomination.THREE); - List cards = List.of(card1, card2); - - CardDeck deck = new CardDeck(List.of(new Card(Pattern.HEART, Denomination.THREE))); - Dealer dealer = new Dealer(cards); - - // when - dealer.hit(deck); - - // then - assertThat(dealer.getCards().size()).isEqualTo(3); - } - - @Test - @DisplayName("딜러의 카드의 총합이 16 이하면 힛 해야 한다.") - void hittable() { - // given - Card card1 = new Card(Pattern.DIAMOND, Denomination.TEN); - Card card2 = new Card(Pattern.CLOVER, Denomination.SIX); - List cards = List.of(card1, card2); - - Dealer dealer = new Dealer(cards); - - // when - boolean actual = dealer.shouldHit(); - - // then - assertThat(actual).isEqualTo(true); - } - - @Test - @DisplayName("딜러의 카드의 총합이 17 이상이면 힛 할 수 없다.") - void notHittable() { - - // given - Card card1 = new Card(Pattern.DIAMOND, Denomination.TEN); - Card card2 = new Card(Pattern.CLOVER, Denomination.SEVEN); - List cards = List.of(card1, card2); - - Dealer dealer = new Dealer(cards); - - // when - boolean actual = dealer.shouldHit(); - - // then - assertThat(actual).isEqualTo(false); - } } diff --git a/src/test/java/blackjack/domain/NameTest.java b/src/test/java/blackjack/domain/participant/NameTest.java similarity index 95% rename from src/test/java/blackjack/domain/NameTest.java rename to src/test/java/blackjack/domain/participant/NameTest.java index bd50893c5d..eec4ae9682 100644 --- a/src/test/java/blackjack/domain/NameTest.java +++ b/src/test/java/blackjack/domain/participant/NameTest.java @@ -1,4 +1,4 @@ -package blackjack.domain; +package blackjack.domain.participant; import static org.assertj.core.api.Assertions.assertThatThrownBy; diff --git a/src/test/java/blackjack/domain/participant/PlayerNamesTest.java b/src/test/java/blackjack/domain/participant/PlayerNamesTest.java new file mode 100644 index 0000000000..c5d3170e0e --- /dev/null +++ b/src/test/java/blackjack/domain/participant/PlayerNamesTest.java @@ -0,0 +1,45 @@ +package blackjack.domain.participant; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PlayerNamesTest { + + @Test + @DisplayName("이름이 중복되면 예외를 반환한다.") + void duplicatedNames() { + // given + List nameStrings = List.of("name", "name"); + + // then + assertThatThrownBy(() -> new PlayerNames(nameStrings)).isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 플레이어 이름은 중복될 수 없습니다."); + } + + @Test + @DisplayName("플레이어의 수가 2인보다 작으면 예외를 반환한다.") + void understaffed() { + // given + List nameStrings = List.of("name"); + + // then + assertThatThrownBy(() -> new PlayerNames(nameStrings)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 2~8인의 플레이어가 참가할 수 있습니다."); + } + + @Test + @DisplayName("플레이어의 수가 2인보다 작으면 예외를 반환한다.") + void overstaffed() { + // given + List nameStrings = List.of("1", "2", "3", "4", "5", "6", "7", "8", "9"); + + // then + assertThatThrownBy(() -> new PlayerNames(nameStrings)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("[ERROR] 2~8인의 플레이어가 참가할 수 있습니다."); + } +} diff --git a/src/test/java/blackjack/domain/participant/PlayerTest.java b/src/test/java/blackjack/domain/participant/PlayerTest.java index 2926609797..1a1a5e6d56 100644 --- a/src/test/java/blackjack/domain/participant/PlayerTest.java +++ b/src/test/java/blackjack/domain/participant/PlayerTest.java @@ -2,14 +2,17 @@ import static org.assertj.core.api.Assertions.assertThat; -import blackjack.domain.Name; +import blackjack.domain.Betting; +import blackjack.domain.Outcome; import blackjack.domain.card.Card; -import blackjack.domain.card.CardDeck; import blackjack.domain.card.Denomination; import blackjack.domain.card.Pattern; +import blackjack.util.BlackjackTestUtil; import java.util.List; 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; public class PlayerTest { @@ -17,11 +20,11 @@ public class PlayerTest { @DisplayName("플레이어는 처음에 모든 카드를 보여준다.") void showEveryCard() { // given - Card card1 = new Card(Pattern.DIAMOND, Denomination.THREE); - Card card2 = new Card(Pattern.CLOVER, Denomination.THREE); + Card card1 = Card.of(Pattern.DIAMOND, Denomination.THREE); + Card card2 = Card.of(Pattern.CLOVER, Denomination.THREE); List cards = List.of(card1, card2); - Player player = new Player(new Name("Player"), cards); + Player player = new Player(new Name("Player"), cards, new Betting(1000)); // when List actual = player.showInitialCards(); @@ -30,58 +33,17 @@ void showEveryCard() { assertThat(actual).containsOnly(card1, card2); } - @Test - @DisplayName("플레이어가 카드 한 장을 더 받는 경우") - void addCard() { - // given - Name name = new Name("pobi"); - Card card1 = new Card(Pattern.DIAMOND, Denomination.THREE); - Card card2 = new Card(Pattern.CLOVER, Denomination.THREE); - List cards = List.of(card1, card2); - - CardDeck deck = new CardDeck(List.of(new Card(Pattern.HEART, Denomination.THREE))); - Player player = new Player(name, cards); - - // when - player.hit(deck); - - // then - assertThat(player.getCards().size()).isEqualTo(3); - } - - @Test - @DisplayName("플레이어의 카드의 총합이 21보다 작으면 hit이 가능하다.") - void hittable() { + @ParameterizedTest + @CsvSource(value = {"WIN_BLACKJACK,1500", "WIN,1000", "DRAW,0", "LOSE,-1000"}) + @DisplayName("승패 결과에 따라 수익을 계산한다.") + void calculateProfit(String outcomeName, int expected) { // given - Name name = new Name("pobi"); - Card card1 = new Card(Pattern.DIAMOND, Denomination.TEN); - Card card2 = new Card(Pattern.CLOVER, Denomination.TEN); - List cards = List.of(card1, card2); - - Player player = new Player(name, cards); - - // when - boolean actual = player.isHittable(); - - // then - assertThat(actual).isEqualTo(true); - } - - @Test - @DisplayName("플레이어의 카드의 총합이 21 이상이면 hit이 불가능하다.") - void notHittable() { - // given - Name name = new Name("pobi"); - Card card1 = new Card(Pattern.DIAMOND, Denomination.TEN); - Card card2 = new Card(Pattern.CLOVER, Denomination.ACE); - List cards = List.of(card1, card2); - - Player player = new Player(name, cards); + Player player = BlackjackTestUtil.createPlayer(21); // when - boolean actual = player.isHittable(); + int profit = player.calculateProfit(Outcome.valueOf(outcomeName)); // then - assertThat(actual).isEqualTo(false); + assertThat(profit).isEqualTo(expected); } } diff --git a/src/test/java/blackjack/domain/participant/PlayersTest.java b/src/test/java/blackjack/domain/participant/PlayersTest.java new file mode 100644 index 0000000000..41f99f280a --- /dev/null +++ b/src/test/java/blackjack/domain/participant/PlayersTest.java @@ -0,0 +1,30 @@ +package blackjack.domain.participant; + +import static org.assertj.core.api.Assertions.assertThat; + +import blackjack.domain.Betting; +import blackjack.domain.card.CardDeck; +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class PlayersTest { + + @Test + @DisplayName("Players 안의 플레이어 리스트를 정상적으로 순회할 수 있다.") + void iterate() { + // given + List nameStrings = List.of("1", "2", "3", "4", "5", "6", "7", "8"); + List actual = new ArrayList<>(); + + Players players = new Players(new PlayerNames(nameStrings), CardDeck.createGameDeck(), + name -> new Betting(1000)); + + // when + players.forEach(player -> actual.add(player.getName())); + + // then + assertThat(actual).containsAll(nameStrings); + } +} diff --git a/src/test/java/blackjack/domain/state/BlackjackTest.java b/src/test/java/blackjack/domain/state/BlackjackTest.java new file mode 100644 index 0000000000..5fc3005aa0 --- /dev/null +++ b/src/test/java/blackjack/domain/state/BlackjackTest.java @@ -0,0 +1,53 @@ +package blackjack.domain.state; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import blackjack.domain.card.Card; +import blackjack.domain.card.Cards; +import blackjack.domain.card.Denomination; +import blackjack.domain.card.Pattern; +import blackjack.util.BlackjackTestUtil; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class BlackjackTest { + + @Test + @DisplayName("Blackjack 상태에서는 hit할 수 없다.") + void cannotHit() { + // given + Cards cards = new Cards(BlackjackTestUtil.createCards(21)); + State blackjack = new Blackjack(cards); + + // then + assertThatThrownBy(() -> blackjack.hit(Card.of(Pattern.CLOVER, Denomination.ACE))) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Test + @DisplayName("Blackjack 상태에서는 stand할 수 없다.") + void cannotStand() { + // given + Cards cards = new Cards(BlackjackTestUtil.createCards(21)); + State blackjack = new Blackjack(cards); + + // then + assertThatThrownBy(blackjack::stand) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Test + @DisplayName("끝났는지 물어보면 true를 반환한다.") + void finishedTrue() { + // given + Cards cards = new Cards(BlackjackTestUtil.createCards(21)); + State blackjack = new Blackjack(cards); + + // when + boolean actual = blackjack.isFinished(); + + // then + assertThat(actual).isTrue(); + } +} diff --git a/src/test/java/blackjack/domain/state/BustTest.java b/src/test/java/blackjack/domain/state/BustTest.java new file mode 100644 index 0000000000..940042d759 --- /dev/null +++ b/src/test/java/blackjack/domain/state/BustTest.java @@ -0,0 +1,56 @@ +package blackjack.domain.state; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import blackjack.domain.card.Card; +import blackjack.domain.card.Cards; +import blackjack.domain.card.Denomination; +import blackjack.domain.card.Pattern; +import blackjack.util.BlackjackTestUtil; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class BustTest { + + @Test + @DisplayName("Bust 상태에서는 hit할 수 없다.") + void cannotHit() { + // given + Cards cards = new Cards(BlackjackTestUtil.createCards(20)); + cards = cards.add(Card.of(Pattern.CLOVER, Denomination.TWO)); + State bust = new Bust(cards); + + // then + assertThatThrownBy(() -> bust.hit(Card.of(Pattern.CLOVER, Denomination.ACE))) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Test + @DisplayName("Bust 상태에서는 stand할 수 없다.") + void cannotStand() { + // given + Cards cards = new Cards(BlackjackTestUtil.createCards(20)); + cards = cards.add(Card.of(Pattern.CLOVER, Denomination.TWO)); + State bust = new Bust(cards); + + // then + assertThatThrownBy(bust::stand) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Test + @DisplayName("끝났는지 물어보면 true를 반환한다.") + void finishedTrue() { + // given + Cards cards = new Cards(BlackjackTestUtil.createCards(20)); + cards = cards.add(Card.of(Pattern.CLOVER, Denomination.TWO)); + State bust = new Bust(cards); + + // when + boolean actual = bust.isFinished(); + + // then + assertThat(actual).isTrue(); + } +} diff --git a/src/test/java/blackjack/domain/state/DealerRunningTest.java b/src/test/java/blackjack/domain/state/DealerRunningTest.java new file mode 100644 index 0000000000..c9fab0f467 --- /dev/null +++ b/src/test/java/blackjack/domain/state/DealerRunningTest.java @@ -0,0 +1,124 @@ +package blackjack.domain.state; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import blackjack.domain.card.Card; +import blackjack.domain.card.Cards; +import blackjack.domain.card.Denomination; +import blackjack.domain.card.Pattern; +import blackjack.util.BlackjackTestUtil; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class DealerRunningTest { + + @Test + @DisplayName("처음 점수 합이 17 미만인 카드를 받으면 Running 상태") + void startedWithRunning() { + // given + List cards = BlackjackTestUtil.createCards(16); + + // when + State actual = DealerRunning.start(cards); + + // then + assertThat(actual).isInstanceOf(DealerRunning.class); + } + + @Test + @DisplayName("딜러가 처음 점수 합이 17 이상인 카드를 받으면 Stand 상태") + void startedWithStand() { + // given + List cards = BlackjackTestUtil.createCards(17); + + // when + State actual = DealerRunning.start(cards); + + // then + assertThat(actual).isInstanceOf(Stand.class); + } + + @Test + @DisplayName("점수 합이 21인 카드를 받으면 Blackjack") + void blackjack() { + // given + List cards = BlackjackTestUtil.createCards(21); + + // when + State actual = DealerRunning.start(cards); + + // then + assertThat(actual).isInstanceOf(Blackjack.class); + } + + @Test + @DisplayName("카드 합이 17 이상이면 Stand해야 한다.") + void stand() { + // given + Cards cards = new Cards(BlackjackTestUtil.createCards(16)); + State running = new DealerRunning(cards); + + // when + State actual = running.hit(Card.of(Pattern.DIAMOND, Denomination.ACE)); + + // then + assertThat(actual).isInstanceOf(Stand.class); + } + + @Test + @DisplayName("딜러는 자체적인 판단으로 Stand 할 수 없다.") + void cannotStand() { + // given + Cards cards = new Cards(BlackjackTestUtil.createCards(16)); + State running = new DealerRunning(cards); + + // then + assertThatThrownBy(running::stand) + .isInstanceOf(UnsupportedOperationException.class) + .hasMessage("[ERROR] 딜러는 스스로 Stand할 수 없습니다."); + } + + @Test + @DisplayName("hit하고 나서 카드 합이 21을 넘으면 Bust 상태가 된다.") + void bust() { + // given + Cards cards = new Cards(BlackjackTestUtil.createCards(16)); + State running = new DealerRunning(cards); + + // when + State actual = running.hit(Card.of(Pattern.HEART, Denomination.SIX)); + + // then + assertThat(actual).isInstanceOf(Bust.class); + } + + @Test + @DisplayName("hit하고 나서 카드 합이 21 이하면 다시 Running 상태가 된다.") + void hit() { + // given + Cards cards = new Cards(BlackjackTestUtil.createCards(15)); + State running = new PlayerRunning(cards); + + // when + State actual = running.hit(Card.of(Pattern.DIAMOND, Denomination.ACE)); + + // then + assertThat(actual).isInstanceOf(Running.class); + } + + @Test + @DisplayName("끝났는지 물어보면 false를 반환한다.") + void finishedFalse() { + // given + Cards cards = new Cards(BlackjackTestUtil.createCards(20)); + State running = new PlayerRunning(cards); + + // when + boolean actual = running.isFinished(); + + // then + assertThat(actual).isFalse(); + } +} diff --git a/src/test/java/blackjack/domain/state/PlayerRunningTest.java b/src/test/java/blackjack/domain/state/PlayerRunningTest.java new file mode 100644 index 0000000000..9769b16b0a --- /dev/null +++ b/src/test/java/blackjack/domain/state/PlayerRunningTest.java @@ -0,0 +1,97 @@ +package blackjack.domain.state; + +import static org.assertj.core.api.Assertions.assertThat; + +import blackjack.domain.card.Card; +import blackjack.domain.card.Cards; +import blackjack.domain.card.Denomination; +import blackjack.domain.card.Pattern; +import blackjack.util.BlackjackTestUtil; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PlayerRunningTest { + + @Test + @DisplayName("플레이어가 처음 카드를 받아서 생성하면 Running 상태") + void started() { + // given + List cards = BlackjackTestUtil.createCards(20); + + // when + State actual = PlayerRunning.start(cards); + + // then + assertThat(actual).isInstanceOf(PlayerRunning.class); + } + + @Test + @DisplayName("점수 합이 21인 카드를 받으면 Blackjack") + void blackjack() { + // given + List cards = BlackjackTestUtil.createCards(21); + + // when + State actual = PlayerRunning.start(cards); + + // then + assertThat(actual).isInstanceOf(Blackjack.class); + } + + @Test + @DisplayName("hit하고 나서 카드 합이 21을 넘으면 Bust 상태가 된다.") + void bust() { + // given + Cards cards = new Cards(BlackjackTestUtil.createCards(20)); + State running = new PlayerRunning(cards); + + // when + State actual = running.hit(Card.of(Pattern.DIAMOND, Denomination.TWO)); + + // then + assertThat(actual).isInstanceOf(Bust.class); + } + + @Test + @DisplayName("hit하고 나서 카드 합이 21 이하면 다시 Running 상태가 된다.") + void hit() { + // given + Cards cards = new Cards(BlackjackTestUtil.createCards(20)); + State running = new PlayerRunning(cards); + + // when + State actual = running.hit(Card.of(Pattern.DIAMOND, Denomination.ACE)); + + // then + assertThat(actual).isInstanceOf(Running.class); + } + + @Test + @DisplayName("Hit 상태에서 stand하면 Stand 상태가 된다.") + void stand() { + // given + Cards cards = new Cards(BlackjackTestUtil.createCards(20)); + State running = new PlayerRunning(cards); + + // when + State actual = running.stand(); + + // then + assertThat(actual).isInstanceOf(Stand.class); + } + + @Test + @DisplayName("끝났는지 물어보면 false를 반환한다.") + void finishedFalse() { + // given + Cards cards = new Cards(BlackjackTestUtil.createCards(20)); + State running = new PlayerRunning(cards); + + // when + boolean actual = running.isFinished(); + + // then + assertThat(actual).isFalse(); + } +} diff --git a/src/test/java/blackjack/domain/state/StandTest.java b/src/test/java/blackjack/domain/state/StandTest.java new file mode 100644 index 0000000000..19ebd9704c --- /dev/null +++ b/src/test/java/blackjack/domain/state/StandTest.java @@ -0,0 +1,53 @@ +package blackjack.domain.state; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import blackjack.domain.card.Card; +import blackjack.domain.card.Cards; +import blackjack.domain.card.Denomination; +import blackjack.domain.card.Pattern; +import blackjack.util.BlackjackTestUtil; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class StandTest { + + @Test + @DisplayName("Stand 상태에서는 hit할 수 없다.") + void cannotHit() { + // given + Cards cards = new Cards(BlackjackTestUtil.createCards(20)); + State stand = new Stand(cards); + + // then + assertThatThrownBy(() -> stand.hit(Card.of(Pattern.CLOVER, Denomination.ACE))) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Test + @DisplayName("Stand 상태에서는 stand할 수 없다.") + void cannotStand() { + // given + Cards cards = new Cards(BlackjackTestUtil.createCards(20)); + State stand = new Stand(cards); + + // then + assertThatThrownBy(stand::stand) + .isInstanceOf(UnsupportedOperationException.class); + } + + @Test + @DisplayName("끝났는지 물어보면 true를 반환한다.") + void finishedTrue() { + // given + Cards cards = new Cards(BlackjackTestUtil.createCards(20)); + State stand = new Stand(cards); + + // when + boolean actual = stand.isFinished(); + + // then + assertThat(actual).isTrue(); + } +} diff --git a/src/test/java/blackjack/util/BlackjackTestUtil.java b/src/test/java/blackjack/util/BlackjackTestUtil.java new file mode 100644 index 0000000000..98cb4a9c87 --- /dev/null +++ b/src/test/java/blackjack/util/BlackjackTestUtil.java @@ -0,0 +1,60 @@ +package blackjack.util; + +import static blackjack.domain.card.Pattern.CLOVER; +import static blackjack.domain.card.Pattern.DIAMOND; +import static blackjack.domain.card.Pattern.HEART; + +import blackjack.domain.Betting; +import blackjack.domain.card.Card; +import blackjack.domain.card.CardDeck; +import blackjack.domain.card.Denomination; +import blackjack.domain.card.Pattern; +import blackjack.domain.participant.Dealer; +import blackjack.domain.participant.Name; +import blackjack.domain.participant.Player; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.NoSuchElementException; +import java.util.stream.Collectors; + +public class BlackjackTestUtil { + + public static Player createPlayer(int expectedScore) { + return new Player(new Name("player"), createCards(expectedScore), new Betting(1000)); + } + + public static Dealer createDealer(int expectedScore) { + return new Dealer(createCards(expectedScore)); + } + + public static List createCards(int expectedScore) { + if (expectedScore <= 3 || expectedScore > 21) { + throw new IllegalArgumentException("불가능한 테스트"); + } + if (expectedScore <= 11) { + return List.of(createCard(CLOVER, 2), createCard(DIAMOND, expectedScore - 2)); + } + if (expectedScore == 21) { + return List.of(createCard(CLOVER, 10), createCard(DIAMOND, 1)); + } + return List.of(createCard(CLOVER, 10), createCard(DIAMOND, expectedScore - 10)); + } + + public static CardDeck createDeck(int expectedScore, int... expectedScores) { + List cards = new ArrayList<>(); + cards.add(createCard(HEART, expectedScore)); + cards.addAll(Arrays.stream(expectedScores) + .mapToObj(score -> createCard(HEART, score)) + .collect(Collectors.toList())); + return new CardDeck(cards); + } + + private static Card createCard(Pattern pattern, int expectedDenomination) { + Denomination actual = Arrays.stream(Denomination.values()) + .filter(denomination -> denomination.getValue() == expectedDenomination) + .findFirst() + .orElseThrow(NoSuchElementException::new); + return Card.of(pattern, actual); + } +}