diff --git a/README.md b/README.md index 556099c4de3..7d9853dca80 100644 --- a/README.md +++ b/README.md @@ -5,3 +5,37 @@ ## 우아한테크코스 코드리뷰 - [온라인 코드 리뷰 과정](https://github.com/woowacourse/woowacourse-docs/blob/master/maincourse/README.md) + +## 기능 요구 사항 + +- 블랙잭 게임은 딜러와 플레이어 중 카드의 합이 21 또는 21에 가장 가까운 숫자를 가지는 쪽이 이기는 게임이다. +- 각 카드는 점수를 가진다. + - 숫자 카드는 카드 숫자로 계산한다. + - King, Queen, Jack은 각각 10으로 계산한다. + - Ace는 1 또는 11로 계산한다. +- 플레이어는 이름을 가진다. + - 이름은 공백으로만 구성될 수 없다. + - 이름은 앞뒤 공백을 가질 수 없다. + - 중복된 이름은 가질 수 없다. + +### 승리 조건 + +- 카드 숫자를 합쳐 가능한 21에 가깝게 만들면 이긴다. + - 처음 받은 2장 합쳐 21이 나오는 경우 블랙잭이 되며, 모든 카드조합 중에 가장 강력하다. + - 카드 숫자의 합이 21을 초과하게 되는 순간 '버스트'라고 하며 딜러의 결과에 관계없이 플레이어가 패배한다. + - 딜러와 플레이어의 점수와 블랙잭 여부가 동일한 경우, 딜러가 승리한다. + +### 프로그램 진행 순서 + +1. 참가자 이름 입력한다. +2. 카드를 2장씩 나눠준 후에, 현재 카드 상태 출력한다. +3. 각 참가자들의 턴을 진행한다. + 1. 카드가 21을 넘은 경우, 턴이 종료된다. + 2. 카드를 더 받을 지 여부를 입력한다. + 3. 만약 더 받는다면, 카드를 한 장 추가하고 1으로 돌아간다. + 4. 더 받지 않을 경우, 턴을 종료한다. +4. 딜러의 턴을 진행한다. + 1. 카드가 16을 넘은 경우, 턴이 종료된다. + 2. 카드를 한 장 더 추가하고, 1로 돌아간다. +5. 딜러 및 모든 참가자의 보유 카드들과 점수를 출력한다. +6. 딜러 및 모든 참가자의 승패를 출력한다. diff --git a/src/main/java/.gitkeep b/src/main/java/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/main/java/blackjack/BlackJackApplication.java b/src/main/java/blackjack/BlackJackApplication.java new file mode 100644 index 00000000000..c508abef182 --- /dev/null +++ b/src/main/java/blackjack/BlackJackApplication.java @@ -0,0 +1,8 @@ +package blackjack; + +public class BlackJackApplication { + public static void main(String[] args) { + BlackJackGame blackJackGame = new BlackJackGame(); + blackJackGame.run(); + } +} diff --git a/src/main/java/blackjack/BlackJackGame.java b/src/main/java/blackjack/BlackJackGame.java new file mode 100644 index 00000000000..9612c621328 --- /dev/null +++ b/src/main/java/blackjack/BlackJackGame.java @@ -0,0 +1,60 @@ +package blackjack; + +import blackjack.domain.card.Deck; +import blackjack.domain.participant.Dealer; +import blackjack.domain.participant.Player; +import blackjack.domain.participant.Players; +import blackjack.view.InputView; +import blackjack.view.OutputView; +import java.util.List; + +public class BlackJackGame { + + private final InputView inputView = new InputView(); + private final OutputView outputView = new OutputView(); + + public void run() { + Deck deck = Deck.createShuffledDeck(); + Dealer dealer = new Dealer(); + Players players = createPlayers(); + drawStartCards(dealer, players, deck); + play(players, dealer, deck); + printResult(dealer, players); + } + + private Players createPlayers() { + List names = inputView.inputPlayerNames(); + return Players.from(names); + } + + private void drawStartCards(Dealer dealer, Players players, Deck deck) { + dealer.drawStartCards(deck); + players.drawStartCards(deck); + outputView.printStartStatus(dealer, players); + } + + private void play(Players players, Dealer dealer, Deck deck) { + players.play(this::playTurn, deck); + while (dealer.isDrawable()) { + outputView.printDealerDraw(); + dealer.add(deck.draw()); + } + } + + private void playTurn(Player player, Deck deck) { + while (player.isDrawable() && inputView.isPlayerWantDraw(player.getName())) { + player.add(deck.draw()); + outputView.printPlayerCards(player); + } + } + + private void printResult(Dealer dealer, Players players) { + outputView.printEndingStatus(dealer, players); + int winCount = dealer.calculateWinCount(players); + int loseCount = dealer.calculateLoseCount(players); + outputView.printDealerMatchResult(winCount, loseCount); + for (Player player : players.getPlayers()) { + outputView.printPlayerMatchResult(player.getName(), player.isWin(dealer)); + } + } +} diff --git a/src/main/java/blackjack/domain/card/Card.java b/src/main/java/blackjack/domain/card/Card.java new file mode 100644 index 00000000000..416457c0642 --- /dev/null +++ b/src/main/java/blackjack/domain/card/Card.java @@ -0,0 +1,51 @@ +package blackjack.domain.card; + +import java.util.Objects; + +public class Card { + + private final Value value; + private final Shape shape; + + public Card(Value value, Shape shape) { + this.value = Objects.requireNonNull(value); + this.shape = Objects.requireNonNull(shape); + } + + public int getMinScore() { + return value.getMinScore(); + } + + public int getMaxScore() { + return value.getMaxScore(); + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + Card card = (Card) object; + return value == card.value && shape == card.shape; + } + + @Override + public int hashCode() { + return Objects.hash(value, shape); + } + + public Value getValue() { + return value; + } + + public Shape getShape() { + return shape; + } + + public boolean isAce() { + return value.isAce(); + } +} diff --git a/src/main/java/blackjack/domain/card/Deck.java b/src/main/java/blackjack/domain/card/Deck.java new file mode 100644 index 00000000000..3c759f38d33 --- /dev/null +++ b/src/main/java/blackjack/domain/card/Deck.java @@ -0,0 +1,40 @@ +package blackjack.domain.card; + +import static java.util.stream.Collectors.toList; + +import java.util.Arrays; +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; + +public class Deck { + + private final Queue cards; + + private Deck(List cards) { + this.cards = new LinkedList<>(cards); + } + + public static Deck createShuffledDeck() { + List cards = Arrays.stream(Shape.values()) + .map(Deck::makeCards) + .flatMap(List::stream) + .collect(toList()); + Collections.shuffle(cards); + return new Deck(cards); + } + + private static List makeCards(Shape shape) { + return Arrays.stream(Value.values()) + .map(value -> new Card(value, shape)) + .toList(); + } + + public Card draw() { + if (cards.isEmpty()) { + throw new IllegalStateException("카드를 더 이상 뽑을 수 없습니다."); + } + return cards.poll(); + } +} diff --git a/src/main/java/blackjack/domain/card/Hand.java b/src/main/java/blackjack/domain/card/Hand.java new file mode 100644 index 00000000000..6b07ecb2b09 --- /dev/null +++ b/src/main/java/blackjack/domain/card/Hand.java @@ -0,0 +1,67 @@ +package blackjack.domain.card; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Hand { + + private static final int BLACKJACK_SCORE = 21; + private static final int BLACKJACK_SIZE = 2; + + private final List cards; + + public Hand(List cards) { + this.cards = new ArrayList<>(cards); + } + + public void add(Card card) { + cards.add(card); + } + + public int calculateScore() { + int totalMinScore = getMinScore(); + int biggerScore = getBiggerScore(); + + if (biggerScore > BLACKJACK_SCORE) { + return totalMinScore; + } + return biggerScore; + } + + private int getMinScore() { + return cards.stream() + .mapToInt(Card::getMinScore) + .sum(); + } + + private int getBiggerScore() { + int score = getMinScore(); + int differenceScore = cards.stream() + .filter(Card::isAce) + .mapToInt(this::calculateDifferenceScore) + .findAny() + .orElse(0); + return score + differenceScore; + } + + private int calculateDifferenceScore(Card card) { + return card.getMaxScore() - card.getMinScore(); + } + + public boolean isBusted() { + return calculateScore() > BLACKJACK_SCORE; + } + + public boolean isBlackjack() { + return cards.size() == BLACKJACK_SIZE && calculateScore() == BLACKJACK_SCORE; + } + + public List getCards() { + return Collections.unmodifiableList(cards); + } + + public boolean isEmpty() { + return cards.isEmpty(); + } +} diff --git a/src/main/java/blackjack/domain/card/Shape.java b/src/main/java/blackjack/domain/card/Shape.java new file mode 100644 index 00000000000..292d024bab4 --- /dev/null +++ b/src/main/java/blackjack/domain/card/Shape.java @@ -0,0 +1,5 @@ +package blackjack.domain.card; + +public enum Shape { + SPADE, DIAMOND, HEART, CLOVER +} diff --git a/src/main/java/blackjack/domain/card/Value.java b/src/main/java/blackjack/domain/card/Value.java new file mode 100644 index 00000000000..7cd8f2898db --- /dev/null +++ b/src/main/java/blackjack/domain/card/Value.java @@ -0,0 +1,37 @@ +package blackjack.domain.card; + +public enum Value { + ACE(1, 11), + TWO(2, 2), + THREE(3, 3), + FOUR(4, 4), + FIVE(5, 5), + SIX(6, 6), + SEVEN(7, 7), + EIGHT(8, 8), + NINE(9, 9), + TEN(10, 10), + JACK(10, 10), + QUEEN(10, 10), + KING(10, 10); + + private final int minScore; + private final int maxScore; + + Value(int minScore, int maxScore) { + this.minScore = minScore; + this.maxScore = maxScore; + } + + public int getMinScore() { + return minScore; + } + + public int getMaxScore() { + return maxScore; + } + + public boolean isAce() { + return this == ACE; + } +} diff --git a/src/main/java/blackjack/domain/participant/Dealer.java b/src/main/java/blackjack/domain/participant/Dealer.java new file mode 100644 index 00000000000..95d651262f2 --- /dev/null +++ b/src/main/java/blackjack/domain/participant/Dealer.java @@ -0,0 +1,55 @@ +package blackjack.domain.participant; + +import blackjack.domain.card.Card; +import java.util.Collections; +import java.util.List; + +public class Dealer extends Participant { + + private static final int DRAWABLE_MAX_SCORE = 16; + private static final int START_CARD_SIZE = 1; + + public Dealer() { + super(Collections.emptyList()); + } + + Dealer(List cards) { + super(cards); + } + + public boolean isWin(Player player) { + if (player.isBusted() || this.isBlackjack()) { + return true; + } + if (this.isBusted() || player.isBlackjack()) { + return false; + } + return this.calculateScore() >= player.calculateScore(); + } + + private boolean isLose(Player player) { + return !isWin(player); + } + + public int calculateWinCount(Players players) { + return (int) players.getPlayers().stream() + .filter(this::isWin) + .count(); + } + + public int calculateLoseCount(Players players) { + return (int) players.getPlayers().stream() + .filter(this::isLose) + .count(); + } + + @Override + protected int getMaxDrawableScore() { + return DRAWABLE_MAX_SCORE; + } + + @Override + protected int getStartCardSize() { + return START_CARD_SIZE; + } +} diff --git a/src/main/java/blackjack/domain/participant/Name.java b/src/main/java/blackjack/domain/participant/Name.java new file mode 100644 index 00000000000..3baa06e97a9 --- /dev/null +++ b/src/main/java/blackjack/domain/participant/Name.java @@ -0,0 +1,40 @@ +package blackjack.domain.participant; + +import java.util.Objects; + +public class Name { + + private final String name; + + public Name(String name) { + validate(name); + this.name = name.strip(); + } + + private void validate(String name) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException("이름은 적어도 한 글자 이상을 포함해야 합니다."); + } + } + + public String getName() { + return name; + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + Name name1 = (Name) object; + return Objects.equals(name, name1.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +} diff --git a/src/main/java/blackjack/domain/participant/Participant.java b/src/main/java/blackjack/domain/participant/Participant.java new file mode 100644 index 00000000000..53e21f9886e --- /dev/null +++ b/src/main/java/blackjack/domain/participant/Participant.java @@ -0,0 +1,62 @@ +package blackjack.domain.participant; + +import blackjack.domain.card.Card; +import blackjack.domain.card.Deck; +import blackjack.domain.card.Hand; +import java.util.List; + +public abstract class Participant { + + protected static final int BLACKJACK_SCORE = 21; + private static final int START_CARDS_SIZE = 2; + + private final Hand hand; + + protected Participant(List cards) { + this.hand = new Hand(cards); + } + + public final int calculateScore() { + return hand.calculateScore(); + } + + public final boolean isDrawable() { + return calculateScore() <= getMaxDrawableScore(); + } + + public boolean isBlackjack() { + return hand.isBlackjack(); + } + + public final boolean isBusted() { + return hand.isBusted(); + } + + public final void drawStartCards(Deck deck) { + if (!hand.isEmpty()) { + throw new IllegalStateException("이미 시작 카드를 뽑았습니다."); + } + for (int i = 0; i < START_CARDS_SIZE; i++) { + add(deck.draw()); + } + } + + public final void add(Card card) { + if (!isDrawable()) { + throw new IllegalStateException("더 이상 카드를 추가할 수 없습니다."); + } + hand.add(card); + } + + public final List getStartCards() { + return getCards().subList(0, getStartCardSize()); + } + + public final List getCards() { + return hand.getCards(); + } + + protected abstract int getMaxDrawableScore(); + + protected abstract int getStartCardSize(); +} diff --git a/src/main/java/blackjack/domain/participant/Player.java b/src/main/java/blackjack/domain/participant/Player.java new file mode 100644 index 00000000000..a96d63e9e8a --- /dev/null +++ b/src/main/java/blackjack/domain/participant/Player.java @@ -0,0 +1,56 @@ +package blackjack.domain.participant; + +import blackjack.domain.card.Card; +import java.util.Collections; +import java.util.List; +import java.util.Objects; + +public class Player extends Participant { + + private static final int START_CARD_SIZE = 2; + private final Name name; + + Player(List cards, Name name) { + super(cards); + this.name = Objects.requireNonNull(name); + } + + public static Player from(String name) { + return new Player(Collections.emptyList(), new Name(name)); + } + + public boolean isWin(Dealer dealer) { + return !dealer.isWin(this); + } + + @Override + protected int getMaxDrawableScore() { + return BLACKJACK_SCORE; + } + + @Override + protected int getStartCardSize() { + return START_CARD_SIZE; + } + + public String getName() { + return name.getName(); + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (object == null || getClass() != object.getClass()) { + return false; + } + Player player = (Player) object; + return Objects.equals(name, player.name); + } + + @Override + public int hashCode() { + return Objects.hash(name); + } +} diff --git a/src/main/java/blackjack/domain/participant/PlayerTurn.java b/src/main/java/blackjack/domain/participant/PlayerTurn.java new file mode 100644 index 00000000000..53ae62cc63c --- /dev/null +++ b/src/main/java/blackjack/domain/participant/PlayerTurn.java @@ -0,0 +1,8 @@ +package blackjack.domain.participant; + +import blackjack.domain.card.Deck; + +@FunctionalInterface +public interface PlayerTurn { + void play(Player player, Deck deck); +} diff --git a/src/main/java/blackjack/domain/participant/Players.java b/src/main/java/blackjack/domain/participant/Players.java new file mode 100644 index 00000000000..ca19558f47d --- /dev/null +++ b/src/main/java/blackjack/domain/participant/Players.java @@ -0,0 +1,59 @@ +package blackjack.domain.participant; + +import blackjack.domain.card.Deck; +import java.util.List; + +public class Players { + + private static final int MAX_PLAYERS_SIZE = 4; + + private final List players; + + private Players(List players) { + validateSize(players); + validateDistinct(players); + this.players = players; + } + + public static Players from(List names) { + List players = names.stream() + .map(Player::from) + .toList(); + return new Players(players); + } + + private void validateSize(List players) { + if (players.isEmpty()) { + throw new IllegalArgumentException("최소 한 명의 플레이어가 있어야 합니다."); + } + if (players.size() > MAX_PLAYERS_SIZE) { + throw new IllegalArgumentException("최대 4명의 플레이어만 참여 가능합니다."); + } + } + + private void validateDistinct(List players) { + if (isDuplicated(players)) { + throw new IllegalArgumentException("중복된 이름을 사용할 수 없습니다."); + } + } + + private boolean isDuplicated(List players) { + return players.size() != players.stream().distinct().count(); + } + + public void drawStartCards(Deck deck) { + for (Player player : players) { + player.drawStartCards(deck); + } + } + + public List getPlayers() { + return players; + } + + public void play(PlayerTurn playTurn, Deck deck) { + for (Player player : players) { + playTurn.play(player, deck); + } + } +} diff --git a/src/main/java/blackjack/view/InputView.java b/src/main/java/blackjack/view/InputView.java new file mode 100644 index 00000000000..eb40910c95f --- /dev/null +++ b/src/main/java/blackjack/view/InputView.java @@ -0,0 +1,35 @@ +package blackjack.view; + +import java.util.List; +import java.util.Scanner; + +public class InputView { + + private static final Scanner SCANNER = new Scanner(System.in); + private static final String NAME_DELIMITER = ","; + private static final String WANT_DRAW_INPUT = "y"; + private static final String WANT_NOT_DRAW_INPUT = "n"; + + public List inputPlayerNames() { + System.out.println("게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)"); + String names = SCANNER.nextLine(); + return List.of(names.split(NAME_DELIMITER, -1)); + } + + public boolean isPlayerWantDraw(String name) { + System.out.printf("%s는 한장의 카드를 더 받겠습니까?(예는 %s, 아니오는 %s)%n", name, WANT_DRAW_INPUT, WANT_NOT_DRAW_INPUT); + String input = SCANNER.nextLine(); + return isWantDraw(input); + } + + private boolean isWantDraw(String input) { + if (WANT_DRAW_INPUT.equalsIgnoreCase(input)) { + return true; + } + if (WANT_NOT_DRAW_INPUT.equalsIgnoreCase(input)) { + return false; + } + throw new IllegalArgumentException("잘못된 입력입니다. 입력은 (%s/%s) 만 가능합니다." + .formatted(WANT_DRAW_INPUT, WANT_NOT_DRAW_INPUT)); + } +} diff --git a/src/main/java/blackjack/view/OutputView.java b/src/main/java/blackjack/view/OutputView.java new file mode 100644 index 00000000000..c8174e591a9 --- /dev/null +++ b/src/main/java/blackjack/view/OutputView.java @@ -0,0 +1,123 @@ +package blackjack.view; + +import blackjack.domain.card.Card; +import blackjack.domain.card.Shape; +import blackjack.domain.card.Value; +import blackjack.domain.participant.Dealer; +import blackjack.domain.participant.Player; +import blackjack.domain.participant.Players; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class OutputView { + + private static final Map SHAPE_NAME = Map.of( + Shape.HEART, "하트", + Shape.SPADE, "스페이드", + Shape.DIAMOND, "다이아몬드", + Shape.CLOVER, "클로버" + ); + + private static final Map VALUE_NAME = Map.ofEntries( + Map.entry(Value.ACE, "A"), Map.entry(Value.TWO, "2"), + Map.entry(Value.THREE, "3"), Map.entry(Value.FOUR, "4"), + Map.entry(Value.FIVE, "5"), Map.entry(Value.SIX, "6"), + Map.entry(Value.SEVEN, "7"), Map.entry(Value.EIGHT, "8"), + Map.entry(Value.NINE, "9"), Map.entry(Value.TEN, "10"), + Map.entry(Value.JACK, "J"), Map.entry(Value.QUEEN, "Q"), + Map.entry(Value.KING, "K") + ); + + public void printStartStatus(Dealer dealer, Players players) { + System.out.println(); + System.out.println("딜러와 " + toPrintedFormat(players) + "에게 2장을 나누었습니다."); + printDealerCards(dealer.getStartCards()); + System.out.println(); + printPlayersCards(players); + System.out.println(); + } + + public void printEndingStatus(Dealer dealer, Players players) { + System.out.println(); + System.out.println(); + printDealerEndingStatus(dealer); + printPlayersEndingStatus(players); + System.out.println(); + } + + private void printDealerCards(List cards) { + System.out.print("딜러 카드: "); + printCards(cards); + } + + private void printPlayersCards(Players players) { + for (Player player : players.getPlayers()) { + printPlayerCards(player.getName(), player.getStartCards()); + System.out.println(); + } + } + + private void printDealerEndingStatus(Dealer dealer) { + printDealerCards(dealer.getCards()); + printScore(dealer.calculateScore()); + System.out.println(); + } + + private void printPlayerCards(String name, List cards) { + System.out.print(name + "카드: "); + printCards(cards); + } + + private void printPlayersEndingStatus(Players players) { + for (Player player : players.getPlayers()) { + printPlayerCards(player.getName(), player.getCards()); + printScore(player.calculateScore()); + System.out.println(); + } + } + + public void printPlayerCards(Player player) { + printPlayerCards(player.getName(), player.getCards()); + System.out.println(); + } + + private void printCards(List cards) { + String printingFormat = cards.stream() + .map(this::toPrintedFormat) + .collect(Collectors.joining(", ")); + System.out.print(printingFormat); + } + + private void printScore(int score) { + System.out.print(" - 결과 : " + score); + } + + private String toPrintedFormat(Players players) { + return players.getPlayers().stream() + .map(Player::getName) + .collect(Collectors.joining(", ")); + } + + private String toPrintedFormat(Card card) { + return VALUE_NAME.get(card.getValue()) + SHAPE_NAME.get(card.getShape()); + } + + public void printDealerDraw() { + System.out.println(); + System.out.print("딜러는 16이하라 한장의 카드를 더 받았습니다."); + } + + public void printDealerMatchResult(int winCount, int loseCount) { + System.out.println("## 최종 승패"); + System.out.printf("딜러 : %d승 %d패%n", winCount, loseCount); + } + + public void printPlayerMatchResult(String name, boolean isWin) { + if (isWin) { + System.out.println(name + ": 승"); + return; + } + System.out.println(name + ": 패"); + } +} diff --git a/src/test/java/.gitkeep b/src/test/java/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 diff --git a/src/test/java/blackjack/domain/card/CardTest.java b/src/test/java/blackjack/domain/card/CardTest.java new file mode 100644 index 00000000000..3790c5041e7 --- /dev/null +++ b/src/test/java/blackjack/domain/card/CardTest.java @@ -0,0 +1,54 @@ +package blackjack.domain.card; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +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.EnumSource; + +public class CardTest { + + @DisplayName("카드 값에 따라 점수를 확인할 수 있다") + @Nested + class CardScoreTest { + + @DisplayName("숫자 카드는 카드의 숫자 값을 점수로 가진다") + @ParameterizedTest + @CsvSource({"TWO, 2", "TEN, 10"}) + void scoreTest_whenNumberCard(Value value, int expected) { + Card card = new Card(value, Shape.CLOVER); + + assertAll( + () -> assertThat(card.getMinScore()).isEqualTo(expected), + () -> assertThat(card.getMaxScore()).isEqualTo(expected) + ); + } + + @DisplayName("Jack, Queen, King 카드는 모두 10점을 가진다.") + @ParameterizedTest + @EnumSource(names = {"JACK", "QUEEN", "KING"}) + void scoreTest_whenJQK_Card(Value value) { + Card card = new Card(value, Shape.HEART); + + assertAll( + () -> assertThat(card.getMinScore()).isEqualTo(10), + () -> assertThat(card.getMaxScore()).isEqualTo(10) + ); + } + + @DisplayName("Ace 카드는 1, 또는 11점을 가진다.") + @Test + void scoreTest_whenAceCard() { + Card card = new Card(Value.ACE, Shape.HEART); + + assertAll( + () -> assertThat(card.getMinScore()).isEqualTo(1), + () -> assertThat(card.getMaxScore()).isEqualTo(11) + ); + } + } +} diff --git a/src/test/java/blackjack/domain/card/DeckTest.java b/src/test/java/blackjack/domain/card/DeckTest.java new file mode 100644 index 00000000000..d28ee2e53ea --- /dev/null +++ b/src/test/java/blackjack/domain/card/DeckTest.java @@ -0,0 +1,35 @@ +package blackjack.domain.card; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class DeckTest { + + @DisplayName("카드 한 장을 뽑을 수 있다") + @Test + void drawTest() { + Deck deck = Deck.createShuffledDeck(); + + assertThat(deck.draw()).isNotNull(); + } + + @DisplayName("카드는 최대 52장만 뽑을 수 있다.") + @Test + void drawTest_drawTooManyTimes_throwException() { + Deck deck = Deck.createShuffledDeck(); + drawRepeat(deck, 52); + + assertThatThrownBy(() -> deck.draw()) + .isInstanceOf(IllegalStateException.class) + .hasMessage("카드를 더 이상 뽑을 수 없습니다."); + } + + private void drawRepeat(Deck deck, int times) { + for (int i = 0; i < times; i++) { + deck.draw(); + } + } +} diff --git a/src/test/java/blackjack/domain/card/HandTest.java b/src/test/java/blackjack/domain/card/HandTest.java new file mode 100644 index 00000000000..e5b09d9274a --- /dev/null +++ b/src/test/java/blackjack/domain/card/HandTest.java @@ -0,0 +1,128 @@ +package blackjack.domain.card; + +import static org.assertj.core.api.Assertions.assertThat; + +import blackjack.domain.fixture.CardsFixture; +import java.util.Collections; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +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; + +class HandTest { + + @DisplayName("점수를 계산할 수 있다.") + @ParameterizedTest + @MethodSource("cardsAndScore") + void calculateScoreTest(List cards, int expected) { + Hand hand = new Hand(cards); + + assertThat(hand.calculateScore()).isEqualTo(expected); + } + + static Stream cardsAndScore() { + return Stream.of(Arguments.of(CardsFixture.BLACKJACK, 21), + Arguments.of(CardsFixture.TWO_ACE, 12), + Arguments.of(CardsFixture.CARDS_SCORE_16, 16)); + } + + @DisplayName("카드를 한 장 뽑는다") + @Test + void addTest() { + Hand hand = new Hand(CardsFixture.CARDS_SCORE_16); + + Card additionalCard = new Card(Value.ACE, Shape.HEART); + hand.add(additionalCard); + + assertThat(hand.getCards()) + .containsAll(CardsFixture.CARDS_SCORE_16) + .contains(additionalCard) + .hasSize(CardsFixture.CARDS_SCORE_16.size() + 1); + } + + @DisplayName("카드의 버스트 상태를 알 수 있다.") + @Nested + class BustTest { + + @DisplayName("21점이 넘으면 버스트이다.") + @Test + void whenBusted_returnTrue() { + Hand bustedHand = new Hand(CardsFixture.CARDS_SCORE_22); + + assertThat(bustedHand.isBusted()).isTrue(); + } + + @DisplayName("21점 이하 점수는 버스트가 아니다.") + @Test + void whenNotBusted_returnFalse() { + Hand hand = new Hand(CardsFixture.CARDS_SCORE_21); + + assertThat(hand.isBusted()).isFalse(); + } + } + + @DisplayName("카드의 블랙잭 상태를 판단할 수 있다.") + @Nested + class BlackjackTest { + + @DisplayName("21점이면서 2장의 카드라면 블랙잭이다.") + @Test + void whenBlackjack_returnTrue() { + Hand blackjackHand = new Hand(CardsFixture.BLACKJACK); + + assertThat(blackjackHand.isBlackjack()).isTrue(); + } + + @DisplayName("21점 미만 점수는 블랙잭이 아니다.") + @Test + void whenUnderScore_returnFalse() { + Hand blackjackHand = new Hand(List.of( + new Card(Value.KING, Shape.HEART), + new Card(Value.QUEEN, Shape.HEART) + )); + + assertThat(blackjackHand.isBlackjack()).isFalse(); + } + + @DisplayName("21점이지만 3장의 카드라면 블랙잭이 아니다.") + @Test + void whenOverSize_returnFalse() { + Hand blackjackHand = new Hand(CardsFixture.CARDS_SCORE_21); + + assertThat(blackjackHand.isBlackjack()).isFalse(); + } + + @DisplayName("21점 초과 점수는 블랙잭이 아니다.") + @Test + void whenOverScore_returnFalse() { + Hand blackjackHand = new Hand(CardsFixture.CARDS_SCORE_22); + + assertThat(blackjackHand.isBlackjack()).isFalse(); + } + } + + @DisplayName("빈 손패인지 확인할 수 있다.") + @Nested + class EmptyHandTest { + + @DisplayName("아무 카드도 존재하지 않으면 빈 손패이다.") + @Test + void whenEmptyHand_returnTrue() { + Hand emptyHand = new Hand(Collections.emptyList()); + + assertThat(emptyHand.isEmpty()).isTrue(); + } + + @DisplayName("카드가 한 장이라도 존재하면 빈 손패가 아니다.") + @Test + void whenCardExists_returnFalse() { + Hand emptyHand = new Hand(List.of(new Card(Value.ACE, Shape.HEART))); + + assertThat(emptyHand.isEmpty()).isFalse(); + } + } +} diff --git a/src/test/java/blackjack/domain/fixture/CardsFixture.java b/src/test/java/blackjack/domain/fixture/CardsFixture.java new file mode 100644 index 00000000000..8005fbde325 --- /dev/null +++ b/src/test/java/blackjack/domain/fixture/CardsFixture.java @@ -0,0 +1,44 @@ +package blackjack.domain.fixture; + +import blackjack.domain.card.Card; +import blackjack.domain.card.Shape; +import blackjack.domain.card.Value; +import java.util.List; + +public class CardsFixture { + public static final List CARDS_SCORE_4 = List.of( + new Card(Value.TWO, Shape.HEART), + new Card(Value.TWO, Shape.SPADE) + ); + public static final List TWO_ACE = List.of( + new Card(Value.ACE, Shape.HEART), + new Card(Value.ACE, Shape.SPADE) + ); + public static final List CARDS_SCORE_16 = List.of( + new Card(Value.JACK, Shape.HEART), + new Card(Value.SIX, Shape.HEART) + ); + public static final List CARDS_SCORE_17 = List.of( + new Card(Value.JACK, Shape.HEART), + new Card(Value.SEVEN, Shape.HEART) + ); + public static final List CARDS_SCORE_21 = List.of( + new Card(Value.JACK, Shape.HEART), + new Card(Value.EIGHT, Shape.HEART), + new Card(Value.THREE, Shape.HEART) + ); + public static final List BLACKJACK = List.of( + new Card(Value.ACE, Shape.HEART), + new Card(Value.KING, Shape.HEART) + ); + public static final List CARDS_SCORE_22 = List.of( + new Card(Value.JACK, Shape.HEART), + new Card(Value.SEVEN, Shape.HEART), + new Card(Value.FIVE, Shape.HEART) + ); + public static final List BUSTED = List.of( + new Card(Value.KING, Shape.DIAMOND), + new Card(Value.QUEEN, Shape.DIAMOND), + new Card(Value.JACK, Shape.DIAMOND) + ); +} diff --git a/src/test/java/blackjack/domain/participant/DealerTest.java b/src/test/java/blackjack/domain/participant/DealerTest.java new file mode 100644 index 00000000000..f0e911ff9de --- /dev/null +++ b/src/test/java/blackjack/domain/participant/DealerTest.java @@ -0,0 +1,198 @@ +package blackjack.domain.participant; + +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.Deck; +import blackjack.domain.card.Shape; +import blackjack.domain.card.Value; +import blackjack.domain.fixture.CardsFixture; +import java.util.List; +import java.util.stream.Stream; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +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; + +class DealerTest { + private static final Name DEFAULT_NAME = new Name("name"); + + @DisplayName("카드의 총 점수가 16을 넘지 않으면, 카드를 더 뽑을 수 있다") + @Test + void isDrawableTest_whenScoreIsUnderBound_returnTrue() { + Dealer dealer = new Dealer(CardsFixture.CARDS_SCORE_16); + + assertThat(dealer.isDrawable()).isTrue(); + } + + @DisplayName("카드의 총 점수가 17을 넘으면, 카드를 더 뽑을 수 없다") + @Test + void isDrawableTest_whenScoreIsOverBound_returnFalse() { + Dealer dealer = new Dealer(CardsFixture.CARDS_SCORE_17); + + assertThat(dealer.isDrawable()).isFalse(); + } + + @DisplayName("점수를 계산할 수 있다.") + @ParameterizedTest + @MethodSource("cardsAndScore") + void calculateScoreTest(List cards, int expected) { + Dealer dealer = new Dealer(cards); + + assertThat(dealer.calculateScore()).isEqualTo(expected); + } + + static Stream cardsAndScore() { + return Stream.of( + Arguments.of(CardsFixture.BLACKJACK, 21), + Arguments.of(CardsFixture.TWO_ACE, 12), + Arguments.of(CardsFixture.CARDS_SCORE_16, 16) + ); + } + + @DisplayName("게임을 시작할 때는 카드를 두 장 뽑는다.") + @Test + void drawStartCardsTest() { + Dealer dealer = new Dealer(); + Deck deck = Deck.createShuffledDeck(); + + dealer.drawStartCards(deck); + + assertThat(dealer.getCards()).hasSize(2); + } + + @DisplayName("이미 카드를 가지고 있는 경우, 시작 카드를 뽑을 수 없다.") + @Test + void drawStartCardsTest_whenAlreadyStarted_throwException() { + Dealer dealer = new Dealer(CardsFixture.CARDS_SCORE_16); + Deck deck = Deck.createShuffledDeck(); + + assertThatThrownBy(() -> dealer.drawStartCards(deck)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("이미 시작 카드를 뽑았습니다."); + } + + @DisplayName("카드의 총 점수가 16을 넘지 않으면, 카드를 한 장 뽑는다") + @Test + void addTest_whenScoreIsUnderBound() { + Dealer dealer = new Dealer(CardsFixture.CARDS_SCORE_16); + Card additionalCard = new Card(Value.ACE, Shape.HEART); + + dealer.add(additionalCard); + + assertThat(dealer.getCards()) + .containsAll(CardsFixture.CARDS_SCORE_16) + .contains(additionalCard) + .hasSize(3); + } + + @DisplayName("카드의 총 점수가 16을 넘으면, 카드를 뽑을 때 예외가 발생한다.") + @Test + void addTest_whenScoreIsOverBound_throwException() { + Dealer dealer = new Dealer(CardsFixture.CARDS_SCORE_17); + Card card = new Card(Value.ACE, Shape.HEART); + + assertThatThrownBy(() -> dealer.add(card)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("더 이상 카드를 추가할 수 없습니다."); + } + + @DisplayName("플레이어와의 승패를 판단할 수 있다.") + @Nested + class IsWinTest { + + @DisplayName("플레이어가 21을 넘을 경우, 딜러가 이긴다.") + @ParameterizedTest + @MethodSource("dealerCards") + void whenPlayerBusted_dealerWin(List cards) { + Dealer dealer = new Dealer(cards); + Player player = new Player(CardsFixture.BUSTED, DEFAULT_NAME); + + assertThat(dealer.isWin(player)).isTrue(); + } + + static Stream> dealerCards() { + return Stream.of( + CardsFixture.BLACKJACK, + CardsFixture.CARDS_SCORE_4, + CardsFixture.CARDS_SCORE_16, + CardsFixture.BUSTED + ); + } + + @DisplayName("딜러만 21을 넘길 경우, 플레이어가 이긴다.") + @ParameterizedTest + @MethodSource("playerCards") + void whenOnlyDealerBusted_playerWin(List cards) { + Dealer dealer = new Dealer(CardsFixture.BUSTED); + Player player = new Player(cards, DEFAULT_NAME); + + assertThat(dealer.isWin(player)).isFalse(); + } + + static Stream> playerCards() { + return Stream.of( + CardsFixture.BLACKJACK, + CardsFixture.CARDS_SCORE_4, + CardsFixture.CARDS_SCORE_16 + ); + } + + @DisplayName("플레이어만 블랙잭일 경우, 플레이어가 이긴다.") + @Test + void whenPlayerOnlyBlackjack_playerWin() { + Player player = new Player(CardsFixture.BLACKJACK, DEFAULT_NAME); + Dealer dealer = new Dealer(CardsFixture.CARDS_SCORE_21); + + assertThat(dealer.isWin(player)).isFalse(); + } + + @DisplayName("딜러만 블랙잭일 경우, 딜러가 이긴다.") + @Test + void whenDealerOnlyBlackjack_dealerWin() { + Player player = new Player(CardsFixture.CARDS_SCORE_21, DEFAULT_NAME); + Dealer dealer = new Dealer(CardsFixture.BLACKJACK); + + assertThat(dealer.isWin(player)).isTrue(); + } + + @DisplayName("둘 다 21을 넘지 않을 경우, 플레이어가 딜러의 숫자보다 같다면 딜러가 이긴다.") + @ParameterizedTest + @MethodSource("sameCards") + void whenPlayerScoreIsEqualToDealerScore_dealerWin(List cards) { + Player player = new Player(cards, DEFAULT_NAME); + Dealer dealer = new Dealer(cards); + + assertThat(dealer.isWin(player)).isTrue(); + } + + static Stream> sameCards() { + return Stream.of( + CardsFixture.BLACKJACK, + CardsFixture.CARDS_SCORE_21, + CardsFixture.CARDS_SCORE_16 + ); + } + + @DisplayName("둘 다 21보다 작을 경우, 플레이어가 딜러의 숫자보다 크다면 플레이어가 이긴다.") + @Test + void whenPlayerScoreIsBiggerThanDealerScore_playerWin() { + Player player = new Player(CardsFixture.CARDS_SCORE_17, DEFAULT_NAME); + Dealer dealer = new Dealer(CardsFixture.CARDS_SCORE_16); + + assertThat(dealer.isWin(player)).isFalse(); + } + + @DisplayName("둘 다 21보다 작을 경우, 플레이어가 딜러의 숫자보다 작다면 딜러가 이긴다.") + @Test + void whenPlayerScoreIsSmallerThanDealerScore_dealerWin() { + Player player = new Player(CardsFixture.CARDS_SCORE_16, DEFAULT_NAME); + Dealer dealer = new Dealer(CardsFixture.CARDS_SCORE_17); + + assertThat(dealer.isWin(player)).isTrue(); + } + } +} diff --git a/src/test/java/blackjack/domain/participant/NameTest.java b/src/test/java/blackjack/domain/participant/NameTest.java new file mode 100644 index 00000000000..486850bbef3 --- /dev/null +++ b/src/test/java/blackjack/domain/participant/NameTest.java @@ -0,0 +1,32 @@ +package blackjack.domain.participant; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.NullAndEmptySource; +import org.junit.jupiter.params.provider.ValueSource; + +class NameTest { + + @DisplayName("이름은 적어도 한 글자를 가져야 한다.") + @ParameterizedTest + @NullAndEmptySource + @ValueSource(strings = {" ", "\n"}) + void validateTest_whenNameIsEmpty_throwException(String name) { + + assertThatThrownBy(() -> new Name(name)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("이름은 적어도 한 글자 이상을 포함해야 합니다."); + } + + @DisplayName("이름의 앞 뒤 공백을 제거해준다.") + @ParameterizedTest + @ValueSource(strings = {" pobi", "pobi ", " pobi ", "\npobi\t"}) + void validateTest_nameHasStrippedName(String input) { + Name name = new Name(input); + + assertThat(name.getName()).isEqualTo("pobi"); + } +} diff --git a/src/test/java/blackjack/domain/participant/PlayerTest.java b/src/test/java/blackjack/domain/participant/PlayerTest.java new file mode 100644 index 00000000000..5eb9cd71901 --- /dev/null +++ b/src/test/java/blackjack/domain/participant/PlayerTest.java @@ -0,0 +1,125 @@ +package blackjack.domain.participant; + +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.Deck; +import blackjack.domain.card.Shape; +import blackjack.domain.card.Value; +import blackjack.domain.fixture.CardsFixture; +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; + +class PlayerTest { + + private static final Name DEFAULT_NAME = new Name("name"); + + @DisplayName("점수를 계산할 수 있다.") + @ParameterizedTest + @MethodSource("cardsAndScore") + void calculateScoreTest(List cards, int expected) { + Player player = new Player(cards, DEFAULT_NAME); + + assertThat(player.calculateScore()).isEqualTo(expected); + } + + static Stream cardsAndScore() { + return Stream.of( + Arguments.of(CardsFixture.BLACKJACK, 21), + Arguments.of(CardsFixture.TWO_ACE, 12), + Arguments.of(CardsFixture.CARDS_SCORE_16, 16) + ); + } + + @DisplayName("ACE가 11로 계산되었을 때 버스트가 되지 않는다면, ACE를 그대로 계산한다.") + @Test + void calculateScoreTest_usingAceMaxScoreCase() { + Player player = new Player(List.of( + new Card(Value.ACE, Shape.HEART), + new Card(Value.TWO, Shape.HEART) + ), DEFAULT_NAME); + + assertThat(player.calculateScore()).isEqualTo(13); + } + + @DisplayName("ACE가 11로 계산되었을 때 버스트가 된다면, ACE를 1로 계산한다.") + @Test + void calculateScoreTest_usingAceMinScoreCase() { + Player player = new Player(List.of( + new Card(Value.ACE, Shape.HEART), + new Card(Value.KING, Shape.HEART), + new Card(Value.TWO, Shape.HEART) + ), DEFAULT_NAME); + + assertThat(player.calculateScore()).isEqualTo(13); + } + + @DisplayName("카드의 총 점수가 21을 넘지 않으면, 카드를 더 뽑을 수 있다") + @Test + void isDrawableTest_whenScoreIsUnderBound_returnTrue() { + Player player = new Player(CardsFixture.CARDS_SCORE_21, DEFAULT_NAME); + + assertThat(player.isDrawable()).isTrue(); + } + + @DisplayName("카드의 총 점수가 21을 넘으면, 카드를 더 뽑을 수 없다") + @Test + void isDrawableTest_whenScoreIsOverBound_returnFalse() { + Player player = new Player(CardsFixture.CARDS_SCORE_22, DEFAULT_NAME); + + assertThat(player.isDrawable()).isFalse(); + } + + @DisplayName("게임을 시작할 때는 카드를 두 장 뽑는다.") + @Test + void drawStartCardsTest() { + Player player = Player.from("name"); + Deck deck = Deck.createShuffledDeck(); + + player.drawStartCards(deck); + + assertThat(player.getCards()).hasSize(2); + } + + @DisplayName("이미 카드를 가지고 있는 경우, 시작 카드를 뽑을 수 없다.") + @Test + void drawStartCardsTest_whenAlreadyStarted_throwException() { + Player player = new Player(CardsFixture.CARDS_SCORE_16, DEFAULT_NAME); + Deck deck = Deck.createShuffledDeck(); + + assertThatThrownBy(() -> player.drawStartCards(deck)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("이미 시작 카드를 뽑았습니다."); + } + + @DisplayName("카드의 총 점수가 21을 넘지 않으면, 카드를 한 장 뽑는다") + @Test + void addTest_whenScoreIsUnderBound() { + Player player = new Player(CardsFixture.CARDS_SCORE_21, DEFAULT_NAME); + + Card additionalCard = new Card(Value.ACE, Shape.HEART); + player.add(additionalCard); + + assertThat(player.getCards()) + .containsAll(CardsFixture.CARDS_SCORE_21) + .contains(additionalCard) + .hasSize(4); + } + + @DisplayName("카드의 총 점수가 21을 넘으면, 카드를 뽑을 때 예외가 발생한다.") + @Test + void addTest_whenScoreIsOverBound_throwException() { + Player player = new Player(CardsFixture.CARDS_SCORE_22, DEFAULT_NAME); + Card card = new Card(Value.ACE, Shape.HEART); + + assertThatThrownBy(() -> player.add(card)) + .isInstanceOf(IllegalStateException.class) + .hasMessage("더 이상 카드를 추가할 수 없습니다."); + } +} 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 00000000000..a776e175789 --- /dev/null +++ b/src/test/java/blackjack/domain/participant/PlayersTest.java @@ -0,0 +1,57 @@ +package blackjack.domain.participant; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertAll; + +import blackjack.domain.card.Deck; +import java.util.Collections; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PlayersTest { + + @DisplayName("최소 한 명 이상의 플레이어가 존재해야 한다.") + @Test + void validateTest_countOfPlayersIsZero_throwException() { + assertThatThrownBy(() -> Players.from(Collections.emptyList())) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("최소 한 명의 플레이어가 있어야 합니다."); + } + + @DisplayName("N 명 이상의 플레이어를 가지면 예외가 발생한다.") + @Test + void validateTest_tooManyPlayers_throwException() { + List manyNames = List.of("1", "2", "3", "4", "5"); + + assertThatThrownBy(() -> Players.from(manyNames)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("최대 4명의 플레이어만 참여 가능합니다."); + } + + @DisplayName("중복된 이름을 사용하면, 예외가 발생한다.") + @Test + void validateTest_whenNameIsDuplicated_throwException() { + List duplicatedNames = List.of("짱수", "커찬", "커찬"); + + assertThatThrownBy(() -> Players.from(duplicatedNames)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("중복된 이름을 사용할 수 없습니다."); + } + + @DisplayName("게임을 시작할 때 모든 플레이어들은 카드를 두 장 뽑는다.") + @Test + void drawStartCardsTest() { + Players players = Players.from(List.of("1", "2")); + Deck deck = Deck.createShuffledDeck(); + + players.drawStartCards(deck); + + List allPlayers = players.getPlayers(); + assertAll( + () -> assertThat(allPlayers.get(0).getCards()).hasSize(2), + () -> assertThat(allPlayers.get(1).getCards()).hasSize(2) + ); + } +}