diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 00000000000..16e3a77bf29 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,11 @@ +## 기능 요구사항 +* 사용자에게 블랙잭 게임에 참여할 플레이어를 입력받는다. + * 블랙잭 게임의 참가자는 딜러 + 플레이어이다. +* 게임 처음에는 게임의 참가자들은 카드덱에서 무작위로 카드 2장을 받는다. + * 카드덱은 하트, 클로버, 스페이드, 다이아몬드 4개의 모양이 있고 각각 ACE, 2~9, KING, QUEEN, JACK이 있다. + * ACE는 1또는 11로 계산할 수 있고 (합계가 21이 넘으면 1로 계산, 아니면 11), KING, QUEEN, JACK은 각각 10으로 계산한다. +* 플레이어는 처음에 받은 두 장의 카드의 숫자 합계가 21을 넘지 않으면 얼마든지 카드를 더 받을 수 있다. +* 딜러는 처음에 받은 카드 2장의 합계가 16이하라면 반드시 1장의 카드를 추가로 받아야 하고, 17점 이상이면 추가로 받을 수 없다. +* 21이 초과된 플레이어는 무조건 패배한다. +* 21에 가장 가까운 플레이어가 승리한다. +* 게임을 완료한 후 각 플레이어별로 승패를 출력한다. \ No newline at end of file diff --git a/src/main/java/Application.java b/src/main/java/Application.java new file mode 100644 index 00000000000..602543b9b80 --- /dev/null +++ b/src/main/java/Application.java @@ -0,0 +1,10 @@ +import game.controller.GameController; +import view.InputView; +import view.OutputView; + +public class Application { + public static void main(String[] args) { + GameController gameController = new GameController(new InputView(), new OutputView()); + gameController.run(); + } +} diff --git a/src/main/java/card/controller/DrawCard.java b/src/main/java/card/controller/DrawCard.java new file mode 100644 index 00000000000..f86c313863d --- /dev/null +++ b/src/main/java/card/controller/DrawCard.java @@ -0,0 +1,10 @@ +package card.controller; + +public enum DrawCard { + y, + n; + + public boolean isY() { + return this == y; + } +} diff --git a/src/main/java/card/controller/DrawCardRequest.java b/src/main/java/card/controller/DrawCardRequest.java new file mode 100644 index 00000000000..65c7e283510 --- /dev/null +++ b/src/main/java/card/controller/DrawCardRequest.java @@ -0,0 +1,31 @@ +package card.controller; + +import card.validator.DrawCardInputValidator; + +import java.util.List; + +public class DrawCardRequest { + private static final List DRAW_YES = List.of("y", "Y"); + public final DrawCard drawCard; + + private DrawCardRequest(final DrawCard drawCard) { + this.drawCard = drawCard; + } + + public static DrawCardRequest from(final String input) { + DrawCardInputValidator.validate(input); + return new DrawCardRequest(formattedDrawCardInput(input)); + } + + private static DrawCard formattedDrawCardInput(final String input) { + if (DRAW_YES.contains(input)) { + return DrawCard.y; + } + + return DrawCard.n; + } + + public boolean isNotDrawCard() { + return !this.drawCard.isY(); + } +} diff --git a/src/main/java/card/model/Card.java b/src/main/java/card/model/Card.java new file mode 100644 index 00000000000..9726c82954d --- /dev/null +++ b/src/main/java/card/model/Card.java @@ -0,0 +1,11 @@ +package card.model; + +public record Card ( + CardSuit suit, + CardRank rank +) { + @Override + public String toString() { + return "%s%s".formatted(rank.getRank(), suit.getName()); + } +} diff --git a/src/main/java/card/model/CardDeck.java b/src/main/java/card/model/CardDeck.java new file mode 100644 index 00000000000..4b8a105ee03 --- /dev/null +++ b/src/main/java/card/model/CardDeck.java @@ -0,0 +1,25 @@ +package card.model; + +import java.util.*; + +public class CardDeck { + private static final List CARD_EMBLEMS = List.of(CardSuit.CLOVER, CardSuit.HEART, CardSuit.SPADE, CardSuit.DIAMOND); + private static final List CARD_NUMBER = List.of(CardRank.ACE, CardRank.TWO, CardRank.THREE, CardRank.FOUR, CardRank.FIVE, CardRank.SIX, CardRank.SEVEN, CardRank.EIGHT, CardRank.NINE, CardRank.JACK, CardRank.QUEEN, CardRank.KING); + + private final Deque cards; + + public CardDeck() { + List cardList = new ArrayList<>(); + for (CardSuit emblem : CARD_EMBLEMS) { + cardList.addAll(CARD_NUMBER.stream() + .map(cardRank -> new Card(emblem, cardRank)) + .toList()); + } + Collections.shuffle(cardList); + this.cards = new ArrayDeque<>(cardList); + } + + public Card pickCard() { + return this.cards.pollFirst(); + } +} diff --git a/src/main/java/card/model/CardRank.java b/src/main/java/card/model/CardRank.java new file mode 100644 index 00000000000..85caed01835 --- /dev/null +++ b/src/main/java/card/model/CardRank.java @@ -0,0 +1,36 @@ +package card.model; + +public enum CardRank { + ACE(1, "A"), + TWO(2, "2"), + THREE(3, "3"), + FOUR(4, "4"), + FIVE(5, "5"), + SIX(6, "6"), + SEVEN(7, "7"), + EIGHT(8, "8"), + NINE(9, "9"), + JACK(10, "J"), + QUEEN(10, "Q"), + KING(10, "K"); + + private final int score; + private final String rank; + + CardRank(final int score, final String rank) { + this.score = score; + this.rank = rank; + } + + public int getScore() { + return this.score; + } + + public String getRank() { + return this.rank; + } + + public boolean isAce() { + return this == ACE; + } +} diff --git a/src/main/java/card/model/CardSuit.java b/src/main/java/card/model/CardSuit.java new file mode 100644 index 00000000000..f4928cc61c8 --- /dev/null +++ b/src/main/java/card/model/CardSuit.java @@ -0,0 +1,18 @@ +package card.model; + +public enum CardSuit { + CLOVER("클로버"), + HEART("하트"), + SPADE("스페이드"), + DIAMOND("다이아몬드"); + + private final String name; + + CardSuit(final String name) { + this.name = name; + } + + public String getName() { + return this.name; + } +} diff --git a/src/main/java/card/validator/DrawCardInputValidator.java b/src/main/java/card/validator/DrawCardInputValidator.java new file mode 100644 index 00000000000..5a9e6c55216 --- /dev/null +++ b/src/main/java/card/validator/DrawCardInputValidator.java @@ -0,0 +1,9 @@ +package card.validator; + +public class DrawCardInputValidator { + public static void validate(final String drawCardInput) { + if (drawCardInput == null || drawCardInput.isBlank()) { + throw new IllegalArgumentException("카드를 더 뽑을지 y 또는 n을 입력해주세요."); + } + } +} diff --git a/src/main/java/game/controller/GameController.java b/src/main/java/game/controller/GameController.java new file mode 100644 index 00000000000..83d7104187d --- /dev/null +++ b/src/main/java/game/controller/GameController.java @@ -0,0 +1,83 @@ +package game.controller; + +import card.model.CardDeck; +import card.controller.DrawCardRequest; +import game.domain.GameJudgement; +import participant.controller.ParticipantRequest; +import participant.domain.ParticipantRegister; +import participant.model.Participant; +import view.InputView; +import view.OutputView; + +import java.util.List; +import java.util.stream.IntStream; + +public class GameController { + private static final int FIRST_CARD_COUNT = 2; + private static final int DEALER_DRAW_CARD_SCORE = 16; + + private final InputView inputView; + private final OutputView outputView; + + private final CardDeck cardDeck = new CardDeck(); + + public GameController(final InputView inputView, final OutputView outputView) { + this.inputView = inputView; + this.outputView = outputView; + } + + public void run() { + List participants = fetchParticipants(); + firstDrawCards(participants); + drawCardsWithoutDealer(participants); + drawCardDealer(participants.stream() + .filter(Participant::isDealer) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("게임에 딜러가 존재하지 않습니다.")) + ); + + this.outputView.printFinalResult(participants); + this.outputView.printGameJudgement(new GameJudgement(participants)); + } + + private List fetchParticipants() { + ParticipantRequest participantRequest = this.inputView.inputParticipants(); + ParticipantRegister participantRegister = new ParticipantRegister(participantRequest); + return participantRegister.registerParticipants(); + } + + private void firstDrawCards(List participants) { + this.outputView.firstDistributeCard(participants); + for (Participant participant : participants) { + IntStream.range(0, FIRST_CARD_COUNT).forEach(it -> { + participant.addCard(this.cardDeck.pickCard()); + }); + } + this.outputView.printParticipantsCard(participants); + } + + private void drawCardsWithoutDealer(List participants) { + for (Participant participant : participants.stream() + .filter(participant -> !participant.isDealer()) + .toList()) { + choiceDrawCard(participant); + } + } + + private void choiceDrawCard(Participant participant) { + while (true) { + DrawCardRequest drawCardRequest = this.inputView.inputDrawCard(participant); + if (drawCardRequest.isNotDrawCard()) break; + + participant.addCard(this.cardDeck.pickCard()); + this.outputView.printParticipantCard(participant); + } + } + + private void drawCardDealer(Participant participant) { + if (participant.calculateCurrentScore() <= DEALER_DRAW_CARD_SCORE) { + this.outputView.printDealerDrawCard(); + participant.addCard(this.cardDeck.pickCard()); + } + } +} diff --git a/src/main/java/game/domain/GameJudgement.java b/src/main/java/game/domain/GameJudgement.java new file mode 100644 index 00000000000..3db5ecffecf --- /dev/null +++ b/src/main/java/game/domain/GameJudgement.java @@ -0,0 +1,82 @@ +package game.domain; + +import participant.model.Participant; + +import java.util.ArrayList; +import java.util.List; + +public class GameJudgement { + private static final String WIN_PARTICIPANT_MESSAGE = "%s: 승"; + private static final String LOSE_PARTICIPANT_MESSAGE = "%s: 패"; + + private final Participant dealer; + private final List participantWithoutDealers = new ArrayList<>(); + + public GameJudgement(final List participants) { + this.dealer = participants.stream() + .filter(Participant::isDealer) + .findAny() + .orElseThrow(() -> new IllegalArgumentException("게임에 딜러가 존재하지 않습니다.")); + this.participantWithoutDealers.addAll(participants.stream() + .filter(participant -> !participant.isDealer()) + .toList() + ); + } + + public String fetchDealerJudge() { + final int dealerScore = this.dealer.calculateCurrentScore(); + final String dealerJudgeMessage = "딜러: %d승 %d패"; + int winCount = 0; + int loseCount = 0; + + // 21점이 넘어가면 무조건 패배 - 참여자 수만큼 패배한 수로 세팅 + if (this.dealer.isOverWinningMaxScore()) { + loseCount = participantWithoutDealers.size(); + return dealerJudgeMessage.formatted(winCount, loseCount); + } + + winCount = fetchDealerWinningCount(dealerScore); + loseCount = fetchDealerLoseCount(dealerScore); + + return dealerJudgeMessage.formatted(winCount, loseCount); + } + + private int fetchDealerWinningCount(final int dealerScore) { + int winCount; + winCount = participantWithoutDealers.stream() + .map(Participant::calculateCurrentScore) + .filter(participantScore -> dealerScore > participantScore) + .toList() + .size(); + return winCount; + } + + private int fetchDealerLoseCount(final int dealerScore) { + return participantWithoutDealers.stream() + .map(Participant::calculateCurrentScore) + .filter(participantScore -> dealerScore < participantScore) + .toList() + .size(); + } + + public List fetchParticipantJudge() { + return participantWithoutDealers.stream() + .map(participant -> { + String loseMessage = LOSE_PARTICIPANT_MESSAGE.formatted(participant.getName()); + if (participant.isOverWinningMaxScore()) { + return loseMessage; + } + + int participantScore = participant.calculateCurrentScore(); + if (participantWithoutDealers.stream() + .filter(p -> !(p.getName().equals(participant.getName()) || + p.isOverWinningMaxScore())) + .allMatch(innerParticipant -> participantScore > innerParticipant.calculateCurrentScore())) { + return WIN_PARTICIPANT_MESSAGE.formatted(participant.getName()); + } + + return loseMessage; + }) + .toList(); + } +} diff --git a/src/main/java/participant/controller/ParticipantRequest.java b/src/main/java/participant/controller/ParticipantRequest.java new file mode 100644 index 00000000000..f6105fe5831 --- /dev/null +++ b/src/main/java/participant/controller/ParticipantRequest.java @@ -0,0 +1,28 @@ +package participant.controller; + +import participant.validator.ParticipantInputValidator; +import utils.SplitUtils; + +import java.util.Arrays; +import java.util.List; + +public class ParticipantRequest { + private static final String SPLIT_DELIMITER = ","; + + private final List participantNames; + + private ParticipantRequest(final List participantNames) { + this.participantNames = participantNames; + } + + public static ParticipantRequest from(final String participantInput) { + String trimmedInput = participantInput.replaceAll(" ", ""); + ParticipantInputValidator.validate(trimmedInput, SPLIT_DELIMITER); + final String[] splitInput = SplitUtils.split(trimmedInput, SPLIT_DELIMITER); + return new ParticipantRequest(Arrays.asList(splitInput)); + } + + public List getParticipantNames() { + return List.copyOf(participantNames); + } +} diff --git a/src/main/java/participant/domain/ParticipantRegister.java b/src/main/java/participant/domain/ParticipantRegister.java new file mode 100644 index 00000000000..aff4584ac5f --- /dev/null +++ b/src/main/java/participant/domain/ParticipantRegister.java @@ -0,0 +1,31 @@ +package participant.domain; + +import participant.controller.ParticipantRequest; +import participant.model.Participant; + +import java.util.Comparator; +import java.util.List; +import java.util.stream.Collectors; + +public class ParticipantRegister { + private static final String DEALER = "딜러"; + + private final ParticipantRequest participantRequest; + + public ParticipantRegister(final ParticipantRequest participantRequest) { + this.participantRequest = participantRequest; + } + + public List registerParticipants() { + List participants = participantRequest.getParticipantNames().stream() + .map(Participant::new) + .collect(Collectors.toList()); + + participants.add(new Participant(DEALER)); + + // 참여자 리스트 순서 정렬 - 딜러가 앞에 오도록 정렬 + return participants.stream() + .sorted(Comparator.comparing(Participant::getName).reversed()) + .collect(Collectors.toList()); + } +} diff --git a/src/main/java/participant/model/Participant.java b/src/main/java/participant/model/Participant.java new file mode 100644 index 00000000000..9c3898dc85d --- /dev/null +++ b/src/main/java/participant/model/Participant.java @@ -0,0 +1,64 @@ +package participant.model; + +import card.model.Card; + +import java.util.ArrayList; +import java.util.List; + +public class Participant { + private static final int ACE_BOTTOM_SCORE = 1; + private static final int ACE_TOP_SCORE = 11; + private static final int BLACK_JACK_WINNING_MAX = 21; + private static final String DEALER = "딜러"; + + + private final String name; + private final List cards = new ArrayList<>(); + + public Participant(final String name) { + this.name = name; + } + + public void addCard(final Card card) { + this.cards.add(card); + } + + public int calculateCurrentScore() { + // 우선 A 카드 제외하고 점수 합산 + int currentScore = cards.stream() + .filter(card -> !card.rank().isAce()) + .mapToInt(card -> card.rank().getScore()) + .sum(); + + // A 카드 점수 합산 + for (Card card : cards.stream().filter(card -> card.rank().isAce()).toList()) { + currentScore += fetchContainAceCardScore(currentScore); + } + + return currentScore; + } + + private int fetchContainAceCardScore(final int currentScore) { + if (currentScore + ACE_TOP_SCORE > BLACK_JACK_WINNING_MAX) { + return ACE_BOTTOM_SCORE; + } + return ACE_TOP_SCORE; + } + + public boolean isOverWinningMaxScore() { + return calculateCurrentScore() > BLACK_JACK_WINNING_MAX; + } + + public String getName() { + return this.name; + } + + public List getCards() { + return List.copyOf(cards); + } + + public boolean isDealer() { + return DEALER.equals(this.name); + } + +} diff --git a/src/main/java/participant/validator/ParticipantInputValidator.java b/src/main/java/participant/validator/ParticipantInputValidator.java new file mode 100644 index 00000000000..e3b3aab8fbb --- /dev/null +++ b/src/main/java/participant/validator/ParticipantInputValidator.java @@ -0,0 +1,14 @@ +package participant.validator; + +import utils.SplitUtils; + +public class ParticipantInputValidator { + + public static void validate(final String participant, final String delimiter) { + try { + SplitUtils.split(participant, delimiter); + } catch (Exception e) { + throw new IllegalArgumentException("게임 참여할 사람의 이름을 입력할 때 문제가 발생했습니다."); + } + } +} diff --git a/src/main/java/utils/Console.java b/src/main/java/utils/Console.java new file mode 100644 index 00000000000..1a9a3bd3ae5 --- /dev/null +++ b/src/main/java/utils/Console.java @@ -0,0 +1,27 @@ +package utils; + +import java.util.Scanner; + +public class Console { + private static Scanner scanner; + + private Console() {} + + public static String readLine() { + return getInstance().nextLine(); + } + + public static void close() { + if (scanner != null) { + scanner.close(); + scanner = null; + } + } + + private static Scanner getInstance() { + if (scanner == null) { + scanner = new Scanner(System.in); + } + return scanner; + } +} diff --git a/src/main/java/utils/SplitUtils.java b/src/main/java/utils/SplitUtils.java new file mode 100644 index 00000000000..f6be9dc8a49 --- /dev/null +++ b/src/main/java/utils/SplitUtils.java @@ -0,0 +1,7 @@ +package utils; + +public class SplitUtils { + public static String[] split(final String target, final String delimiter) { + return target.split(delimiter); + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 00000000000..1ea37c65ba4 --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,28 @@ +package view; + +import card.controller.DrawCardRequest; +import participant.controller.ParticipantRequest; +import participant.model.Participant; +import utils.Console; + +public class InputView { + public ParticipantRequest inputParticipants() { + System.out.println("게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)"); + try { + return ParticipantRequest.from(Console.readLine()); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + return inputParticipants(); + } + } + + public DrawCardRequest inputDrawCard(final Participant participant) { + System.out.println("%s는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)".formatted(participant.getName())); + try { + return DrawCardRequest.from(Console.readLine()); + } catch (IllegalArgumentException e) { + System.out.println(e.getMessage()); + return inputDrawCard(participant); + } + } +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 00000000000..2af930b9fab --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,56 @@ +package view; + +import card.model.Card; +import game.domain.GameJudgement; +import participant.model.Participant; + +import java.util.List; +import java.util.stream.Collectors; + +public class OutputView { + private static final String JOINING_DELIMITER = ", "; + + public void firstDistributeCard(final List participants) { + final String joiningParticipant = participants.stream() + .filter(participant -> !participant.isDealer()) + .map(Participant::getName) + .collect(Collectors.joining(JOINING_DELIMITER)); + + System.out.println("딜러와 %s에게 2장을 나누었습니다.".formatted(joiningParticipant)); + } + + public void printParticipantsCard(final List participants) { + for (Participant participant : participants) { + printParticipantCard(participant); + } + } + + public void printParticipantCard(final Participant participant) { + final String joiningCard = participant.getCards().stream() + .map(Card::toString) + .collect(Collectors.joining(JOINING_DELIMITER)); + System.out.println("%s카드: %s".formatted(participant.getName(), joiningCard)); + } + + public void printDealerDrawCard() { + System.out.println("딜러는 16이하라 한장의 카드를 더 받았습니다."); + } + + public void printFinalResult(final List participants) { + for (Participant participant : participants) { + String joiningCard = participant.getCards().stream() + .map(Card::toString) + .collect(Collectors.joining(JOINING_DELIMITER)); + int score = participant.calculateCurrentScore(); + + System.out.println("%s 카드: %s - 결과: %d".formatted(participant.getName(), joiningCard, score)); + } + } + + public void printGameJudgement(final GameJudgement gameJudgement) { + System.out.println("## 최종 승패"); + System.out.println(gameJudgement.fetchDealerJudge()); + gameJudgement.fetchParticipantJudge().forEach(System.out::println); + } + +} diff --git a/src/test/java/model/ParticipantTest.java b/src/test/java/model/ParticipantTest.java new file mode 100644 index 00000000000..83cb0e38418 --- /dev/null +++ b/src/test/java/model/ParticipantTest.java @@ -0,0 +1,49 @@ +package model; + +import card.model.Card; +import card.model.CardRank; +import card.model.CardSuit; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import participant.model.Participant; + +import java.util.List; +import java.util.stream.Stream; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; + +public class ParticipantTest { + + @Test + void 참가자가_딜러인지_확인하기() { + Participant sut = new Participant("딜러"); + assertThat(sut.isDealer()).isTrue(); + } + + @ParameterizedTest + @MethodSource("provideCard") + void 참가자의_현재_카드_점수_합산(List cards) { + Participant sut = new Participant("name"); + cards.forEach(sut::addCard); + + assertThat(sut.calculateCurrentScore()).isEqualTo(21); + } + + @ParameterizedTest + @MethodSource("provideCard") + void 참가자의_현재_카드_점수_합산이_오버되었는지_확인하기(List cards) { + Participant sut = new Participant("name"); + cards.forEach(sut::addCard); + + assertThat(sut.isOverWinningMaxScore()).isFalse(); + } + + private static Stream provideCard() { + return Stream.of( + Arguments.of(List.of(new Card(CardSuit.CLOVER, CardRank.ACE), new Card(CardSuit.CLOVER, CardRank.JACK))), + Arguments.of(List.of(new Card(CardSuit.HEART, CardRank.ACE), new Card(CardSuit.SPADE, CardRank.ACE), new Card(CardSuit.DIAMOND, CardRank.KING), new Card(CardSuit.DIAMOND, CardRank.NINE))) + ); + } +} diff --git a/src/test/java/view/IOTest.java b/src/test/java/view/IOTest.java new file mode 100644 index 00000000000..a09654339c1 --- /dev/null +++ b/src/test/java/view/IOTest.java @@ -0,0 +1,25 @@ +package view; + +import org.junit.jupiter.api.BeforeEach; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.PrintStream; + +public abstract class IOTest { + private ByteArrayOutputStream byteArrayOutputStream; + + protected void systemIn(final String input) { + System.setIn(new ByteArrayInputStream(input.getBytes())); + } + + @BeforeEach + void setUp() { + byteArrayOutputStream = new ByteArrayOutputStream(); + System.setOut(new PrintStream(byteArrayOutputStream)); + } + + protected String fetchOutput() { + return byteArrayOutputStream.toString(); + } +} diff --git a/src/test/java/view/OutputViewTest.java b/src/test/java/view/OutputViewTest.java new file mode 100644 index 00000000000..40e5718e500 --- /dev/null +++ b/src/test/java/view/OutputViewTest.java @@ -0,0 +1,119 @@ +package view; + +import card.model.Card; +import card.model.CardRank; +import card.model.CardSuit; +import game.domain.GameJudgement; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; +import participant.model.Participant; + +import java.util.List; +import java.util.stream.Collectors; +import java.util.stream.Stream; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.assertAll; + +public class OutputViewTest extends IOTest { + + @Test + void 게임_참가자에게_최초_카드_분배한다는_메시지_출력_테스트() { + OutputView sut = new OutputView(); + + sut.firstDistributeCard(createParticipants(List.of("딜러", "pobi"))); + + assertThat(fetchOutput()).contains("딜러와 pobi에게 2장을 나누었습니다."); + } + + @ParameterizedTest + @MethodSource("provideCard") + void 참가자들의_현재_카드_상황_메시지_출력_테스트(String name, List dealerCards, List participantCards) { + List participants = createParticipants(List.of("딜러", name)); + dealerCards.forEach(card -> participants.get(0).addCard(card)); + participantCards.forEach(card -> participants.get(1).addCard(card)); + + OutputView sut = new OutputView(); + + String dealerCardJoining = dealerCards.stream() + .map(Card::toString) + .collect(Collectors.joining(", ")); + + String participantCardJoining = participantCards.stream() + .map(Card::toString) + .collect(Collectors.joining(", ")); + + sut.printParticipantsCard(participants); + + assertAll( + () -> assertThat(fetchOutput()).contains("딜러카드: %s".formatted(dealerCardJoining)), + () -> assertThat(fetchOutput()).contains("%s카드: %s".formatted(name, participantCardJoining)) + ); + } + + @ParameterizedTest + @MethodSource("provideCard") + void 최종_카드의_점수_현황_메시지_출력_테스트(String name, List dealerCards, List participantCards) { + List participants = createParticipants(List.of("딜러", name)); + Participant dealer = participants.get(0); + Participant participant = participants.get(1); + + dealerCards.forEach(dealer::addCard); + participantCards.forEach(participant::addCard); + + OutputView sut = new OutputView(); + + String dealerCardJoining = dealerCards.stream() + .map(Card::toString) + .collect(Collectors.joining(", ")); + + String participantCardJoining = participantCards.stream() + .map(Card::toString) + .collect(Collectors.joining(", ")); + + sut.printFinalResult(participants); + + assertAll( + () -> assertThat(fetchOutput()).contains("딜러 카드: %s - 결과: %d".formatted(dealerCardJoining, dealer.calculateCurrentScore())), + () -> assertThat(fetchOutput()).contains("%s 카드: %s - 결과: %d".formatted(name, participantCardJoining, participant.calculateCurrentScore())) + ); + } + + @ParameterizedTest + @MethodSource("provideCard") + void 최종_승패_판정_메시지_출력_테스트(String name, List dealerCards, List participantCards) { + List participants = createParticipants(List.of("딜러", name)); + Participant dealer = participants.get(0); + Participant participant = participants.get(1); + + dealerCards.forEach(dealer::addCard); + participantCards.forEach(participant::addCard); + + OutputView sut = new OutputView(); + + sut.printGameJudgement(new GameJudgement(participants)); + + assertThat(fetchOutput()).contains("%s: 승".formatted(name)); + } + + private List createParticipants(final List names) { + return names.stream() + .map(Participant::new) + .toList(); + } + + private static Stream provideCard() { + return Stream.of( + Arguments.of("pobi", + List.of(new Card(CardSuit.CLOVER, CardRank.TWO), new Card(CardSuit.CLOVER, CardRank.FIVE)), + List.of(new Card(CardSuit.CLOVER, CardRank.NINE), new Card(CardSuit.CLOVER, CardRank.QUEEN)) + ), + Arguments.of("pobi", + List.of(new Card(CardSuit.HEART, CardRank.NINE), new Card(CardSuit.HEART, CardRank.EIGHT)), + List.of(new Card(CardSuit.HEART, CardRank.JACK), new Card(CardSuit.HEART, CardRank.ACE)) + ) + ); + } +}