From 4d992f1ca668d954548c78aaaecbcf8575b3e1e4 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Wed, 4 Mar 2026 17:44:01 +0900 Subject: [PATCH 001/129] docs(README): add feature list --- README.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/README.md b/README.md index 1ff5f7b6790..3fca254c00c 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,16 @@ # java-blackjack 블랙잭 미션 저장소 + +## 입출력 +### 입력 +1. 플레이어의 이름을 입력받는다. + - **(예외 처리)** 공백 문자열인 경우 +2. hit(`y`) 혹은 stand(`n`)를 입력받는다. + - **(예외 처리)** `y` 혹은 `n`이 아닌 경우 + +### 출력 +1. 게임 안내 문구를 출력한다. +2. 초기 카드 내역을 출력한다. +3. 최종 카드 내역을 출력한다. +4. 최종 승패 결과를 출력한다. From 37074a5204c2d5900c27d66477f946e05c65e4de Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 5 Mar 2026 10:47:27 +0900 Subject: [PATCH 002/129] feat: add name input feature --- src/main/java/.gitkeep | 0 src/main/java/Main.java | 5 +++++ src/main/java/view/InputView.java | 17 +++++++++++++++++ 3 files changed, 22 insertions(+) delete mode 100644 src/main/java/.gitkeep create mode 100644 src/main/java/Main.java create mode 100644 src/main/java/view/InputView.java 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/Main.java b/src/main/java/Main.java new file mode 100644 index 00000000000..6054a646969 --- /dev/null +++ b/src/main/java/Main.java @@ -0,0 +1,5 @@ +public class Main { + public static void main(String[] args) { + + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 00000000000..7fc407c1c19 --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,17 @@ +package view; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Arrays; +import java.util.List; + +public class InputView { + private static final String DELIMITER = ","; + + public List readNames() throws IOException { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + + return Arrays.stream(br.readLine().split(DELIMITER)).toList(); + } +} From 070e5ba6c274dc9fd4108cf9e102916110323c66 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 5 Mar 2026 10:58:14 +0900 Subject: [PATCH 003/129] feat: add hit or stand input feature --- src/main/java/common/ErrorMessage.java | 15 +++++++++++++++ src/main/java/view/InputView.java | 17 +++++++++++++++++ 2 files changed, 32 insertions(+) create mode 100644 src/main/java/common/ErrorMessage.java diff --git a/src/main/java/common/ErrorMessage.java b/src/main/java/common/ErrorMessage.java new file mode 100644 index 00000000000..5c47edfe02b --- /dev/null +++ b/src/main/java/common/ErrorMessage.java @@ -0,0 +1,15 @@ +package common; + +public enum ErrorMessage { + HIT_OR_STAND_VALUE_MIS_MATCH("y 혹은 n만 입력 가능합니다."); + + private final String message; + + ErrorMessage(String message) { + this.message = message; + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 7fc407c1c19..8a9bc726b6a 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,5 +1,6 @@ package view; +import common.ErrorMessage; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; @@ -8,10 +9,26 @@ public class InputView { private static final String DELIMITER = ","; + private static final List HIT_OR_STAND = List.of("y", "n"); public List readNames() throws IOException { BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); return Arrays.stream(br.readLine().split(DELIMITER)).toList(); } + + public String readHitOrStand() throws IOException { + BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); + String input = br.readLine().trim(); + + validateHitOrStandValue(input); + + return br.readLine().trim(); + } + + private void validateHitOrStandValue(String input) { + if (!HIT_OR_STAND.contains(input)) { + throw new IllegalArgumentException(ErrorMessage.HIT_OR_STAND_VALUE_MIS_MATCH.getMessage()); + } + } } From 0e85d535f378f92f3fc81b049854bae84c91e352 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 5 Mar 2026 11:12:10 +0900 Subject: [PATCH 004/129] feat: add game instruction output feature --- src/main/java/view/OutputView.java | 54 ++++++++++++++++++++++++++++++ 1 file changed, 54 insertions(+) create mode 100644 src/main/java/view/OutputView.java diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 00000000000..fb847531549 --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,54 @@ +package view; + +import java.util.List; + +public class OutputView { + private static final String NAME_PROMPT = "게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)"; + private static final String INITIAL_CARD_SHARE = "딜러와 %s에게 2장을 나누었습니다.\n"; + private static final String HIT_OR_STAND_PROMPT = "%s는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)\n"; + private static final String ADDITIONAL_CARD_FOR_DEALER_DESCRIPTION = "딜러는 16이하라 한장의 카드를 더 받았습니다."; + + public void printNamePrompt() { + System.out.println(NAME_PROMPT); + } + + public void printInitialCardShare(List names) { + System.out.printf(INITIAL_CARD_SHARE, String.join(", ", names)); + } + + public void printHitOrStandPrompt(String name) { + System.out.printf(HIT_OR_STAND_PROMPT, name); + } + + public void printAdditionalCardForDealerDescription() { + System.out.println(ADDITIONAL_CARD_FOR_DEALER_DESCRIPTION); + } +} + +//게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리) +//pobi,jason +// +// 딜러와 pobi, jason에게 2장을 나누었습니다. +//딜러카드: 3다이아몬드 +//pobi카드: 2하트, 8스페이드 +//jason카드: 7클로버, K스페이드 +// +//pobi는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n) +//y +//pobi카드: 2하트, 8스페이드, A클로버 +//pobi는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n) +//n +//jason는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n) +//n +//jason카드: 7클로버, K스페이드 +// +//딜러는 16이하라 한장의 카드를 더 받았습니다. +// +// 딜러카드: 3다이아몬드, 9클로버, 8다이아몬드 - 결과: 20 +//pobi카드: 2하트, 8스페이드, A클로버 - 결과: 21 +//jason카드: 7클로버, K스페이드 - 결과: 17 +// +// ## 최종 승패 +//딜러: 1승 1패 +//pobi: 승 +//jason: 패 \ No newline at end of file From f121afeb0d4981c8d77dff936c612c1d79175173 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 5 Mar 2026 11:47:23 +0900 Subject: [PATCH 005/129] docs : add domains features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 딜러와 플레이어를 추가하였고, 그들의 공통 조상인 참가자를 추가함. 이후 카드와 카드들을 관리하는 덱의 정의를 추가함. --- README.md | 63 ++++++++++++++++++++++++++++++++++++++++++++++++------- 1 file changed, 55 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index 3fca254c00c..91ef0bff395 100644 --- a/README.md +++ b/README.md @@ -3,14 +3,61 @@ 블랙잭 미션 저장소 ## 입출력 + ### 입력 -1. 플레이어의 이름을 입력받는다. - - **(예외 처리)** 공백 문자열인 경우 -2. hit(`y`) 혹은 stand(`n`)를 입력받는다. - - **(예외 처리)** `y` 혹은 `n`이 아닌 경우 + +[x] 1. 플레이어의 이름을 입력받는다. + +- **(예외 처리)** 공백 문자열인 경우 + +[x] 2. hit(`y`) 혹은 stand(`n`)를 입력받는다. + +- **(예외 처리)** `y` 혹은 `n`이 아닌 경우 ### 출력 -1. 게임 안내 문구를 출력한다. -2. 초기 카드 내역을 출력한다. -3. 최종 카드 내역을 출력한다. -4. 최종 승패 결과를 출력한다. + +[x] 1. 게임 안내 문구를 출력한다. + +[ ] 2. 초기 카드 내역을 출력한다. + +[ ] 3. 최종 카드 내역을 출력한다. + +[ ] 4. 최종 승패 결과를 출력한다. + +## 도메인 + +### Participant + +[ ] 초기 카드 2장을 받는다. + +### Dealer + +[ ] 카드를 받는다. + +- 점수 합이 16 이하면 카드를 한장 더 받는다. + +### Player + +[ ] 카드를 받는다 + +- `hit`를 선택하면 한 장 더 받는다. + +### Card + +[ ] 카드 점수 계산 + +- 숫자 카드는 숫자 그대로, J/Q/K는 10점 + +### Deck + +[ ] 카드 합계 + +- 여러 카드의 점수 합산 + +[ ] 버스트 판정 + +- 합계가 21 초과인지 판단 + +[ ] Ace의 1/11 처리 + +- 합계에 따라 1 또는 11로 계산 \ No newline at end of file From e2d44c28e9e9ef59eac26e84775d89a238b5b528 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 5 Mar 2026 17:08:55 +0900 Subject: [PATCH 006/129] test : add card contents test --- src/test/java/domain/CardContentsTest.java | 31 ++++++++++++++++++++++ 1 file changed, 31 insertions(+) create mode 100644 src/test/java/domain/CardContentsTest.java diff --git a/src/test/java/domain/CardContentsTest.java b/src/test/java/domain/CardContentsTest.java new file mode 100644 index 00000000000..44ab67cd79f --- /dev/null +++ b/src/test/java/domain/CardContentsTest.java @@ -0,0 +1,31 @@ +package domain; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class CardContentsTest { + + @Test + @DisplayName("카드 숫자를 잘 제공한다") + void getCardNumber_success() { + //given + CardContents ACE = CardContents.A; + //when + String cardNumber = ACE.getNumber(); + //then + Assertions.assertThat(cardNumber).isEqualTo("A"); + } + + @Test + @DisplayName("카드 점수를 잘 제공한다") + void getCardScore_success() { + //given + CardContents tenCard = CardContents.TEN; + //when + int score = tenCard.getScore(); + //then + Assertions.assertThat(score).isEqualTo(10); + } + +} \ No newline at end of file From 8582f6d3697abe8ec10d93ef90e1d9a1c4c6858d Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 5 Mar 2026 17:09:24 +0900 Subject: [PATCH 007/129] feat : add card and enum class for card --- src/main/java/domain/Card.java | 15 ++++++++++++ src/main/java/domain/CardContents.java | 33 ++++++++++++++++++++++++++ src/main/java/domain/CardShape.java | 5 ++++ 3 files changed, 53 insertions(+) create mode 100644 src/main/java/domain/Card.java create mode 100644 src/main/java/domain/CardContents.java create mode 100644 src/main/java/domain/CardShape.java diff --git a/src/main/java/domain/Card.java b/src/main/java/domain/Card.java new file mode 100644 index 00000000000..193eff79f67 --- /dev/null +++ b/src/main/java/domain/Card.java @@ -0,0 +1,15 @@ +package domain; + +public class Card { + private final CardShape cardShape; + private final CardContents cardContents; + + public Card(CardShape cardShape, CardContents cardContents) { + this.cardShape = cardShape; + this.cardContents = cardContents; + } + + //getter setter 테스트 ??? + + +} diff --git a/src/main/java/domain/CardContents.java b/src/main/java/domain/CardContents.java new file mode 100644 index 00000000000..2606f1f21f8 --- /dev/null +++ b/src/main/java/domain/CardContents.java @@ -0,0 +1,33 @@ +package domain; + +public enum CardContents { + A("A", 0), //TODO : score 어떻게 초기 설정 + 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), + J("J", 10), + Q("Q", 10), + K("K", 10); + + private final String number; // 카드 번호 + private final int score; + + CardContents(String number, int score) { + this.number = number; + this.score = score; + } + + public String getNumber() { + return number; + } + + public int getScore() { + return score; + } +} diff --git a/src/main/java/domain/CardShape.java b/src/main/java/domain/CardShape.java new file mode 100644 index 00000000000..b8d688334c2 --- /dev/null +++ b/src/main/java/domain/CardShape.java @@ -0,0 +1,5 @@ +package domain; + +public enum CardShape { + 스페이드, 하트, 다이아몬드, 클로버 +} From 11fb3ead940061230227dbe168e980e8a71a219d Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 5 Mar 2026 17:26:16 +0900 Subject: [PATCH 008/129] test : add deck creation test --- src/test/java/domain/DeckTest.java | 15 +++++++++++++++ 1 file changed, 15 insertions(+) create mode 100644 src/test/java/domain/DeckTest.java diff --git a/src/test/java/domain/DeckTest.java b/src/test/java/domain/DeckTest.java new file mode 100644 index 00000000000..137886fac45 --- /dev/null +++ b/src/test/java/domain/DeckTest.java @@ -0,0 +1,15 @@ +package domain; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class DeckTest { + @Test + @DisplayName("Deck를 생성할 때 오류 발생 안함") + void deck_create_success() { + Assertions.assertDoesNotThrow( + Deck::createDeck + ); + } +} \ No newline at end of file From 644c62b266a72f944b3ab3edd92e17829ff1db5c Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 5 Mar 2026 17:26:30 +0900 Subject: [PATCH 009/129] feat : add deck creation feature --- src/main/java/domain/Deck.java | 28 ++++++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/main/java/domain/Deck.java diff --git a/src/main/java/domain/Deck.java b/src/main/java/domain/Deck.java new file mode 100644 index 00000000000..0d4b2a3e866 --- /dev/null +++ b/src/main/java/domain/Deck.java @@ -0,0 +1,28 @@ +package domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class Deck { + private final List cards; + + private Deck(List cards) { + this.cards = cards; + } + + public static Deck createDeck() { + List cards = new ArrayList<>(); + createAllCards(cards); + Collections.shuffle(cards); + return new Deck(cards); + } + + private static void createAllCards(List cards) { + for (CardShape shape : CardShape.values()) { + for (CardContents content : CardContents.values()) { + cards.add(new Card(shape, content)); + } + } + } +} From b80246c25d5cc72806bd4569cae70713e0955692 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 5 Mar 2026 17:26:46 +0900 Subject: [PATCH 010/129] docs : check what I implemented --- README.md | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 91ef0bff395..ff8c8cb6a35 100644 --- a/README.md +++ b/README.md @@ -44,12 +44,14 @@ ### Card -[ ] 카드 점수 계산 +[x] 카드 점수 계산 - 숫자 카드는 숫자 그대로, J/Q/K는 10점 ### Deck +[x] 카드 덱 생성 + 셔플 + [ ] 카드 합계 - 여러 카드의 점수 합산 From 3a0177543db898e5761f1707dc7714fc367882b1 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 5 Mar 2026 18:01:22 +0900 Subject: [PATCH 011/129] test : add deck test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 신규 덱 제공 기능 테스트 - 카드 뽑기 기능 테스트 추가(정상, 실패) --- src/test/java/domain/DeckTest.java | 39 ++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/src/test/java/domain/DeckTest.java b/src/test/java/domain/DeckTest.java index 137886fac45..e8167a458d9 100644 --- a/src/test/java/domain/DeckTest.java +++ b/src/test/java/domain/DeckTest.java @@ -1,10 +1,26 @@ package domain; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import common.ErrorMessage; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; class DeckTest { + + Deck deck; + + @BeforeEach + void init() { + deck = Deck.createDeck(); + } + + @Test @DisplayName("Deck를 생성할 때 오류 발생 안함") void deck_create_success() { @@ -12,4 +28,27 @@ void deck_create_success() { Deck::createDeck ); } + + @Test + @DisplayName("Deck에서 카드를 뽑아서 새로운 덱을 만들어줌") + void give_initial_deck_success() { + assertThat(deck.giveInitialDeck()).isInstanceOf(Deck.class); + } + + @ParameterizedTest + @ValueSource(ints = {1, 52}) + @DisplayName("Deck에서 카드를 원하는 장수만큼 뽑아줌") + void draw_card_success(int count) { + assertThat(deck.drawCard(count).size()).isEqualTo(count); + } + + @ParameterizedTest + @ValueSource(ints = {0, 53}) + @DisplayName("Deck에서 0이하 혹은 남은 카드 이상의 숫자 선택 시도 시 오류 발생") + void draw_card_fail(int count) { + assertThatThrownBy( + () -> deck.drawCard(count) + ).isInstanceOf(IllegalArgumentException.class) + .hasMessage(ErrorMessage.DRAW_CARD_OUT_OF_RANGE.getMessage()); + } } \ No newline at end of file From ab9bdbc572c6d2e13d9401d86786570d7b75ecd4 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 5 Mar 2026 18:01:55 +0900 Subject: [PATCH 012/129] feat : add DRAW_CARD_OUT_OF_RANGE --- src/main/java/common/ErrorMessage.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/common/ErrorMessage.java b/src/main/java/common/ErrorMessage.java index 5c47edfe02b..6bbf3b285d6 100644 --- a/src/main/java/common/ErrorMessage.java +++ b/src/main/java/common/ErrorMessage.java @@ -1,7 +1,8 @@ package common; public enum ErrorMessage { - HIT_OR_STAND_VALUE_MIS_MATCH("y 혹은 n만 입력 가능합니다."); + HIT_OR_STAND_VALUE_MIS_MATCH("y 혹은 n만 입력 가능합니다."), + DRAW_CARD_OUT_OF_RANGE("양수 이상의 숫자 중 남은 카드 수 만큼만 선택 가능"); private final String message; From 85717979c139fba47eb617d26183f5565d0334d1 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 5 Mar 2026 18:02:35 +0900 Subject: [PATCH 013/129] feat : add new deck features MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 새로운 덱 제공 기능 - 카드 선택 기능 을 추가하였습니다. --- src/main/java/domain/Deck.java | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/main/java/domain/Deck.java b/src/main/java/domain/Deck.java index 0d4b2a3e866..33aba30b449 100644 --- a/src/main/java/domain/Deck.java +++ b/src/main/java/domain/Deck.java @@ -1,10 +1,12 @@ package domain; +import common.ErrorMessage; import java.util.ArrayList; import java.util.Collections; import java.util.List; public class Deck { + private static final int INIT_DECK_SIZE = 2; private final List cards; private Deck(List cards) { @@ -25,4 +27,23 @@ private static void createAllCards(List cards) { } } } + + public Deck giveInitialDeck() { + return new Deck( + this.drawCard(INIT_DECK_SIZE) + ); + } + + public List drawCard(int count) { + + if (count > cards.size() || count < 1) { + throw new IllegalArgumentException(ErrorMessage.DRAW_CARD_OUT_OF_RANGE.getMessage()); + } + + ArrayList selectedCards = new ArrayList<>(); + for (int i = 0; i < count; i++) { + selectedCards.add(cards.removeFirst()); + } + return selectedCards; + } } From b8d0a7988e2105d19fe011281936501b24f254e7 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 5 Mar 2026 21:41:07 +0900 Subject: [PATCH 014/129] test: add participant deck creation and cards draw --- src/test/java/domain/DeckTest.java | 48 +++++++++++++++++++----------- 1 file changed, 31 insertions(+), 17 deletions(-) diff --git a/src/test/java/domain/DeckTest.java b/src/test/java/domain/DeckTest.java index e8167a458d9..4438fcdec53 100644 --- a/src/test/java/domain/DeckTest.java +++ b/src/test/java/domain/DeckTest.java @@ -7,6 +7,7 @@ import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; 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.ValueSource; @@ -20,7 +21,6 @@ void init() { deck = Deck.createDeck(); } - @Test @DisplayName("Deck를 생성할 때 오류 발생 안함") void deck_create_success() { @@ -30,25 +30,39 @@ void deck_create_success() { } @Test - @DisplayName("Deck에서 카드를 뽑아서 새로운 덱을 만들어줌") - void give_initial_deck_success() { - assertThat(deck.giveInitialDeck()).isInstanceOf(Deck.class); + @DisplayName("참가자의 Deck를 생성할 때 오류 발생 안함") + void initial_deck_create_success() { + Assertions.assertDoesNotThrow( + () -> Deck.createParticipantDeck(deck) + ); } - @ParameterizedTest - @ValueSource(ints = {1, 52}) - @DisplayName("Deck에서 카드를 원하는 장수만큼 뽑아줌") - void draw_card_success(int count) { - assertThat(deck.drawCard(count).size()).isEqualTo(count); + /* + @Test + @DisplayName("여러 장의 카드의 합을 구함") + void calculate_card_score_sum() { + List cards + assertThat(deck.calculateCardScoreSum()).isEqualTo(340); } - @ParameterizedTest - @ValueSource(ints = {0, 53}) - @DisplayName("Deck에서 0이하 혹은 남은 카드 이상의 숫자 선택 시도 시 오류 발생") - void draw_card_fail(int count) { - assertThatThrownBy( - () -> deck.drawCard(count) - ).isInstanceOf(IllegalArgumentException.class) - .hasMessage(ErrorMessage.DRAW_CARD_OUT_OF_RANGE.getMessage()); + */ + @Nested + class drawTest { + @ParameterizedTest + @ValueSource(ints = {1, 52}) + @DisplayName("Deck에서 카드를 원하는 장수만큼 뽑아줌") + void draw_card_success(int count) { + assertThat(deck.drawCard(count).size()).isEqualTo(count); + } + + @ParameterizedTest + @ValueSource(ints = {0, 53}) + @DisplayName("Deck에서 0이하 혹은 남은 카드 이상의 숫자 선택 시도 시 오류 발생") + void draw_card_fail(int count) { + assertThatThrownBy( + () -> deck.drawCard(count) + ).isInstanceOf(IllegalArgumentException.class) + .hasMessage(ErrorMessage.DRAW_CARD_OUT_OF_RANGE.getMessage()); + } } } \ No newline at end of file From 6c65b8c3e970b5c28dce5c1314c89f87596bbbbb Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 5 Mar 2026 21:44:30 +0900 Subject: [PATCH 015/129] feat: add participant deck creation feature --- src/main/java/domain/Deck.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/domain/Deck.java b/src/main/java/domain/Deck.java index 33aba30b449..a35547470e2 100644 --- a/src/main/java/domain/Deck.java +++ b/src/main/java/domain/Deck.java @@ -20,6 +20,10 @@ public static Deck createDeck() { return new Deck(cards); } + public static Deck createParticipantDeck(Deck deck) { + return new Deck(deck.drawCard(INIT_DECK_SIZE)); + } + private static void createAllCards(List cards) { for (CardShape shape : CardShape.values()) { for (CardContents content : CardContents.values()) { @@ -28,12 +32,6 @@ private static void createAllCards(List cards) { } } - public Deck giveInitialDeck() { - return new Deck( - this.drawCard(INIT_DECK_SIZE) - ); - } - public List drawCard(int count) { if (count > cards.size() || count < 1) { @@ -46,4 +44,5 @@ public List drawCard(int count) { } return selectedCards; } + } From fac0ae3981176887275c8a3aa246c7b615572e9d Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 5 Mar 2026 21:45:47 +0900 Subject: [PATCH 016/129] docs: check what I implemented --- README.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index ff8c8cb6a35..8ae1366fd4b 100644 --- a/README.md +++ b/README.md @@ -50,8 +50,12 @@ ### Deck -[x] 카드 덱 생성 + 셔플 +[x] 카드 덱 생성 +- 전체 카드 덱 + 셔플 +- 참가자 카드 덱 생성 + +[x] 카드 N장 뽑기 [ ] 카드 합계 - 여러 카드의 점수 합산 From 4772f2ada0ade45c3c10bb73409897adaf37d8f5 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 5 Mar 2026 22:22:37 +0900 Subject: [PATCH 017/129] refactor: seperate card creation responsibility --- .../java/domain/CardCreationStrategy.java | 7 ++++++ src/main/java/domain/Deck.java | 16 ++---------- .../domain/RandomCardCreationStrategy.java | 25 +++++++++++++++++++ src/test/java/domain/DeckTest.java | 15 ++++++++--- .../RandomCardCreationStrategyTest.java | 24 ++++++++++++++++++ 5 files changed, 70 insertions(+), 17 deletions(-) create mode 100644 src/main/java/domain/CardCreationStrategy.java create mode 100644 src/main/java/domain/RandomCardCreationStrategy.java create mode 100644 src/test/java/domain/RandomCardCreationStrategyTest.java diff --git a/src/main/java/domain/CardCreationStrategy.java b/src/main/java/domain/CardCreationStrategy.java new file mode 100644 index 00000000000..85b31b6c773 --- /dev/null +++ b/src/main/java/domain/CardCreationStrategy.java @@ -0,0 +1,7 @@ +package domain; + +import java.util.List; + +public interface CardCreationStrategy { + List create(); +} diff --git a/src/main/java/domain/Deck.java b/src/main/java/domain/Deck.java index a35547470e2..f53f98a19c4 100644 --- a/src/main/java/domain/Deck.java +++ b/src/main/java/domain/Deck.java @@ -2,7 +2,6 @@ import common.ErrorMessage; import java.util.ArrayList; -import java.util.Collections; import java.util.List; public class Deck { @@ -13,10 +12,8 @@ private Deck(List cards) { this.cards = cards; } - public static Deck createDeck() { - List cards = new ArrayList<>(); - createAllCards(cards); - Collections.shuffle(cards); + public static Deck createDeck(CardCreationStrategy strategy) { + List cards = strategy.create(); return new Deck(cards); } @@ -24,14 +21,6 @@ public static Deck createParticipantDeck(Deck deck) { return new Deck(deck.drawCard(INIT_DECK_SIZE)); } - private static void createAllCards(List cards) { - for (CardShape shape : CardShape.values()) { - for (CardContents content : CardContents.values()) { - cards.add(new Card(shape, content)); - } - } - } - public List drawCard(int count) { if (count > cards.size() || count < 1) { @@ -44,5 +33,4 @@ public List drawCard(int count) { } return selectedCards; } - } diff --git a/src/main/java/domain/RandomCardCreationStrategy.java b/src/main/java/domain/RandomCardCreationStrategy.java new file mode 100644 index 00000000000..3a106a00742 --- /dev/null +++ b/src/main/java/domain/RandomCardCreationStrategy.java @@ -0,0 +1,25 @@ +package domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class RandomCardCreationStrategy implements CardCreationStrategy { + @Override + public List create() { + List cards = createAllCards(); + Collections.shuffle(cards); + return cards; + } + + private List createAllCards() { + List cards = new ArrayList<>(); + for (CardShape shape : CardShape.values()) { + for (CardContents content : CardContents.values()) { + cards.add(new Card(shape, content)); + } + } + + return cards; + } +} diff --git a/src/test/java/domain/DeckTest.java b/src/test/java/domain/DeckTest.java index 4438fcdec53..e6161d57206 100644 --- a/src/test/java/domain/DeckTest.java +++ b/src/test/java/domain/DeckTest.java @@ -4,6 +4,7 @@ import static org.assertj.core.api.Assertions.assertThatThrownBy; import common.ErrorMessage; +import java.util.List; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -13,19 +14,27 @@ import org.junit.jupiter.params.provider.ValueSource; class DeckTest { - Deck deck; + CardCreationStrategy fixedCardCreationStrategy = new CardCreationStrategy() { + @Override + public List create() { + Card spadeJ = new Card(CardShape.스페이드, CardContents.J); + Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); + + return List.of(spadeJ, clover5); + } + }; @BeforeEach void init() { - deck = Deck.createDeck(); + deck = Deck.createDeck(fixedCardCreationStrategy); //TODO : 전략 넣기 } @Test @DisplayName("Deck를 생성할 때 오류 발생 안함") void deck_create_success() { Assertions.assertDoesNotThrow( - Deck::createDeck + () -> Deck.createDeck(fixedCardCreationStrategy) ); } diff --git a/src/test/java/domain/RandomCardCreationStrategyTest.java b/src/test/java/domain/RandomCardCreationStrategyTest.java new file mode 100644 index 00000000000..8dca2a4f5b9 --- /dev/null +++ b/src/test/java/domain/RandomCardCreationStrategyTest.java @@ -0,0 +1,24 @@ +package domain; + +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class RandomCardCreationStrategyTest { + + @Test + @DisplayName("52개의 카드를 잘 만듦") + void creation_success() { + //given + int expectCardSize = 52; + CardCreationStrategy strategy = new RandomCardCreationStrategy(); + + //when + List cards = strategy.create(); + + //then + Assertions.assertThat(cards.size()).isEqualTo(expectCardSize); + } + +} \ No newline at end of file From debd9c00df93adcd4b2c29fe4f6be00cab6e968d Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 5 Mar 2026 22:52:19 +0900 Subject: [PATCH 018/129] refactor: change ace default card score --- src/main/java/domain/CardContents.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/domain/CardContents.java b/src/main/java/domain/CardContents.java index 2606f1f21f8..af9703eb6b2 100644 --- a/src/main/java/domain/CardContents.java +++ b/src/main/java/domain/CardContents.java @@ -1,7 +1,7 @@ package domain; public enum CardContents { - A("A", 0), //TODO : score 어떻게 초기 설정 + A("A", Integer.MAX_VALUE), TWO("2", 2), THREE("3", 3), FOUR("4", 4), From 5598c3962ec5ea994364ea4480af68a971a654af Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Thu, 5 Mar 2026 23:00:03 +0900 Subject: [PATCH 019/129] feat: add card sum calculation excluding aces MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Ace 값 판정과 전체 카드 점수 합 기능은 상호 의존성을 띄기 때문에 Ace는 제외한 카드 점수 합을 계산하는 기능을 추가함. --- README.md | 2 +- src/main/java/domain/Card.java | 8 +++-- src/main/java/domain/Deck.java | 16 +++++++++ src/test/java/domain/CardTest.java | 52 ++++++++++++++++++++++++++++++ src/test/java/domain/DeckTest.java | 20 ++++++++---- 5 files changed, 89 insertions(+), 9 deletions(-) create mode 100644 src/test/java/domain/CardTest.java diff --git a/README.md b/README.md index 8ae1366fd4b..b0172f7e7c5 100644 --- a/README.md +++ b/README.md @@ -56,7 +56,7 @@ - 참가자 카드 덱 생성 [x] 카드 N장 뽑기 -[ ] 카드 합계 +[x] Ace카드를 제외한 카드들의 합계 - 여러 카드의 점수 합산 diff --git a/src/main/java/domain/Card.java b/src/main/java/domain/Card.java index 193eff79f67..c1192b12223 100644 --- a/src/main/java/domain/Card.java +++ b/src/main/java/domain/Card.java @@ -9,7 +9,11 @@ public Card(CardShape cardShape, CardContents cardContents) { this.cardContents = cardContents; } - //getter setter 테스트 ??? - + public CardContents getCardContents() { + return cardContents; + } + public boolean isAce() { + return cardContents.getNumber().equals("A"); + } } diff --git a/src/main/java/domain/Deck.java b/src/main/java/domain/Deck.java index f53f98a19c4..9850a0dff66 100644 --- a/src/main/java/domain/Deck.java +++ b/src/main/java/domain/Deck.java @@ -33,4 +33,20 @@ public List drawCard(int count) { } return selectedCards; } + + public int calculateCardScoreSumExceptAce() { + int sum = 0; + for (Card card : cards) { + sum = addCardScoreExceptAce(card, sum); + } + + return sum; + } + + private int addCardScoreExceptAce(Card card, int sum) { + if (!card.isAce()) { + sum += card.getCardContents().getScore(); + } + return sum; + } } diff --git a/src/test/java/domain/CardTest.java b/src/test/java/domain/CardTest.java new file mode 100644 index 00000000000..3f8caa6c922 --- /dev/null +++ b/src/test/java/domain/CardTest.java @@ -0,0 +1,52 @@ +package domain; + +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +public class CardTest { + @Test + @DisplayName("카드 콘텐츠 잘 제공") + void getCardContents_success() { + //given + CardContents testCardContents = CardContents.J; + Card testCard = new Card(CardShape.스페이드, testCardContents); + + //when + CardContents result = testCard.getCardContents(); + + //then + Assertions.assertThat(result).isEqualTo(testCardContents); + } + + @Nested + class isAceTest { + @Test + void isAce_true() { + //given + Card card = new Card(CardShape.하트, CardContents.A); + + //when + boolean result = card.isAce(); + + //then + assertTrue(result); + } + + @Test + void isAce_false() { + //given + Card card = new Card(CardShape.하트, CardContents.J); + + //when + boolean result = card.isAce(); + + //then + assertFalse(result); + } + } +} diff --git a/src/test/java/domain/DeckTest.java b/src/test/java/domain/DeckTest.java index e6161d57206..77c70a02e70 100644 --- a/src/test/java/domain/DeckTest.java +++ b/src/test/java/domain/DeckTest.java @@ -46,15 +46,23 @@ void initial_deck_create_success() { ); } - /* @Test - @DisplayName("여러 장의 카드의 합을 구함") - void calculate_card_score_sum() { - List cards - assertThat(deck.calculateCardScoreSum()).isEqualTo(340); + @DisplayName("Ace를 제외한 나머지 카드들의 합을 구함") + void calculate_card_score_sum_except_ace() { + CardCreationStrategy fixedCardCreationStrategy = new CardCreationStrategy() { + @Override + public List create() { + Card spadeJ = new Card(CardShape.스페이드, CardContents.J); + Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); + Card diamondAce = new Card(CardShape.다이아몬드, CardContents.A); + + return List.of(spadeJ, clover5, diamondAce); + } + }; + Deck deck = Deck.createDeck(fixedCardCreationStrategy); + assertThat(deck.calculateCardScoreSumExceptAce()).isEqualTo(15); } - */ @Nested class drawTest { @ParameterizedTest From 12c235eed9823da9f08a63dd5d0362e183081884 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 6 Mar 2026 00:13:11 +0900 Subject: [PATCH 020/129] feat: add ace score discrimination feature --- .../java/domain/AceScoreDiscriminator.java | 33 +++++++++++++ .../domain/AceScoreDiscriminatorTest.java | 49 +++++++++++++++++++ 2 files changed, 82 insertions(+) create mode 100644 src/main/java/domain/AceScoreDiscriminator.java create mode 100644 src/test/java/domain/AceScoreDiscriminatorTest.java diff --git a/src/main/java/domain/AceScoreDiscriminator.java b/src/main/java/domain/AceScoreDiscriminator.java new file mode 100644 index 00000000000..d4acdd22aab --- /dev/null +++ b/src/main/java/domain/AceScoreDiscriminator.java @@ -0,0 +1,33 @@ +package domain; + +import java.util.List; + +public class AceScoreDiscriminator { + private static final int BUST_CRITERIA = 21; + + public int calculateAceCardsSum(List cards, int sumExceptAce) { + int aceCount = countAce(cards); + + int aceSum = 0; + for (int i = 0; i < aceCount; i++) { + int leftAceCount = aceCount - (1 + i); + if ((sumExceptAce + aceSum) + 11 + leftAceCount <= BUST_CRITERIA) { + aceSum += 11; + } else { + aceSum += 1; + } + } + return aceSum; + } + + private int countAce(List cards) { + int count = 0; + for (Card card : cards) { + if (card.isAce()) { + count++; + } + } + + return count; + } +} diff --git a/src/test/java/domain/AceScoreDiscriminatorTest.java b/src/test/java/domain/AceScoreDiscriminatorTest.java new file mode 100644 index 00000000000..40157e01ed8 --- /dev/null +++ b/src/test/java/domain/AceScoreDiscriminatorTest.java @@ -0,0 +1,49 @@ +package domain; + +import java.util.List; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class AceScoreDiscriminatorTest { + + private final AceScoreDiscriminator discriminator = new AceScoreDiscriminator(); + + @Test + @DisplayName("Ace 1개 있을 때 값 판별 잘함") + void calculateAceCardSum_success() { + //given + List testCards = List.of( + new Card(CardShape.스페이드, CardContents.A), + new Card(CardShape.클로버, CardContents.TEN) + ); + int testSumExceptAce = 10; + int expectedAceCardsSum = 11; + + //when + int result = discriminator.calculateAceCardsSum(testCards, testSumExceptAce); + + //then + Assertions.assertEquals(expectedAceCardsSum, result); + } + + @Test + @DisplayName("Ace 2개 이상 있을 때 값 판별 잘함") + void calculateAceCardsSum_success() { + //given + List testCards = List.of( + new Card(CardShape.스페이드, CardContents.A), + new Card(CardShape.클로버, CardContents.A), + new Card(CardShape.클로버, CardContents.TEN) + ); + int testSumExceptAce = 10; + int expectedAceCardsSum = 2; + + //when + int result = discriminator.calculateAceCardsSum(testCards, testSumExceptAce); + + //then + Assertions.assertEquals(expectedAceCardsSum, result); + } + +} From 430f617315b9d1fdc95f9b7e79b88d8fe1ddb82b Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 6 Mar 2026 00:16:45 +0900 Subject: [PATCH 021/129] feat: add card score related feature - add card score total sum feature - add bust discrimination feature --- README.md | 10 +++--- src/main/java/domain/Deck.java | 15 +++++++- src/test/java/domain/DeckTest.java | 56 +++++++++++++++++++++++++----- 3 files changed, 68 insertions(+), 13 deletions(-) diff --git a/README.md b/README.md index b0172f7e7c5..4bd44db07d0 100644 --- a/README.md +++ b/README.md @@ -38,7 +38,7 @@ ### Player -[ ] 카드를 받는다 +[ ] 카드를 받는다. - `hit`를 선택하면 한 장 더 받는다. @@ -60,10 +60,12 @@ - 여러 카드의 점수 합산 -[ ] 버스트 판정 +[x] 버스트 판정 - 합계가 21 초과인지 판단 -[ ] Ace의 1/11 처리 +[x] Ace의 1/11 처리 + +- 합계에 따라 1 또는 11로 계산 -- 합계에 따라 1 또는 11로 계산 \ No newline at end of file +[ ] 카드 넣기 \ No newline at end of file diff --git a/src/main/java/domain/Deck.java b/src/main/java/domain/Deck.java index 9850a0dff66..4b1c34724b1 100644 --- a/src/main/java/domain/Deck.java +++ b/src/main/java/domain/Deck.java @@ -6,6 +6,7 @@ public class Deck { private static final int INIT_DECK_SIZE = 2; + private static final int BUST_CRITERIA = 21; private final List cards; private Deck(List cards) { @@ -34,7 +35,18 @@ public List drawCard(int count) { return selectedCards; } - public int calculateCardScoreSumExceptAce() { + public boolean isBust() { + return calculateCardScoreSum() > BUST_CRITERIA; + } + + public int calculateCardScoreSum() { + int sumExceptAce = calculateCardScoreSumExceptAce(); + int sumAce = new AceScoreDiscriminator().calculateAceCardsSum(cards, sumExceptAce); + + return sumAce + sumExceptAce; + } + + private int calculateCardScoreSumExceptAce() { int sum = 0; for (Card card : cards) { sum = addCardScoreExceptAce(card, sum); @@ -43,6 +55,7 @@ public int calculateCardScoreSumExceptAce() { return sum; } + private int addCardScoreExceptAce(Card card, int sum) { if (!card.isAce()) { sum += card.getCardContents().getScore(); diff --git a/src/test/java/domain/DeckTest.java b/src/test/java/domain/DeckTest.java index 77c70a02e70..6c93e2ff183 100644 --- a/src/test/java/domain/DeckTest.java +++ b/src/test/java/domain/DeckTest.java @@ -2,10 +2,13 @@ import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; import common.ErrorMessage; +import java.util.ArrayList; import java.util.List; -import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -21,7 +24,7 @@ public List create() { Card spadeJ = new Card(CardShape.스페이드, CardContents.J); Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); - return List.of(spadeJ, clover5); + return new ArrayList<>(List.of(spadeJ, clover5)); } }; @@ -33,7 +36,7 @@ void init() { @Test @DisplayName("Deck를 생성할 때 오류 발생 안함") void deck_create_success() { - Assertions.assertDoesNotThrow( + assertDoesNotThrow( () -> Deck.createDeck(fixedCardCreationStrategy) ); } @@ -41,14 +44,14 @@ void deck_create_success() { @Test @DisplayName("참가자의 Deck를 생성할 때 오류 발생 안함") void initial_deck_create_success() { - Assertions.assertDoesNotThrow( + assertDoesNotThrow( () -> Deck.createParticipantDeck(deck) ); } @Test - @DisplayName("Ace를 제외한 나머지 카드들의 합을 구함") - void calculate_card_score_sum_except_ace() { + @DisplayName("카드들의 점수 합을 구함") + void calculate_card_score_sum() { CardCreationStrategy fixedCardCreationStrategy = new CardCreationStrategy() { @Override public List create() { @@ -60,13 +63,13 @@ public List create() { } }; Deck deck = Deck.createDeck(fixedCardCreationStrategy); - assertThat(deck.calculateCardScoreSumExceptAce()).isEqualTo(15); + assertThat(deck.calculateCardScoreSum()).isEqualTo(16); } @Nested class drawTest { @ParameterizedTest - @ValueSource(ints = {1, 52}) + @ValueSource(ints = {1, 2}) @DisplayName("Deck에서 카드를 원하는 장수만큼 뽑아줌") void draw_card_success(int count) { assertThat(deck.drawCard(count).size()).isEqualTo(count); @@ -82,4 +85,41 @@ void draw_card_fail(int count) { .hasMessage(ErrorMessage.DRAW_CARD_OUT_OF_RANGE.getMessage()); } } + + @Nested + class isBustTest { + @Test + @DisplayName("카드의 합이 21이 넘어가면 버스트로 판정") + void isBust_true() { + CardCreationStrategy fixedCardCreationStrategy = new CardCreationStrategy() { + @Override + public List create() { + Card spadeJ = new Card(CardShape.스페이드, CardContents.J); + Card cloverQ = new Card(CardShape.클로버, CardContents.Q); + Card diamondK = new Card(CardShape.다이아몬드, CardContents.K); + + return List.of(spadeJ, cloverQ, diamondK); + } + }; + Deck deck = Deck.createDeck(fixedCardCreationStrategy); + assertTrue(deck.isBust()); + } + + @Test + @DisplayName("카드의 합이 21이 넘어가면 버스트로 판정하지 않음") + void isBust_false() { + CardCreationStrategy fixedCardCreationStrategy = new CardCreationStrategy() { + @Override + public List create() { + Card spadeJ = new Card(CardShape.스페이드, CardContents.J); + Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); + Card diamond3 = new Card(CardShape.다이아몬드, CardContents.THREE); + + return List.of(spadeJ, clover5, diamond3); + } + }; + Deck deck = Deck.createDeck(fixedCardCreationStrategy); + assertFalse(deck.isBust()); + } + } } \ No newline at end of file From 80d3469605ae3c0e26bcf24f9a4c51e42bc30744 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 6 Mar 2026 00:43:50 +0900 Subject: [PATCH 022/129] feat: add card addition feature --- README.md | 2 +- src/main/java/domain/Card.java | 16 +++++++++ src/main/java/domain/Deck.java | 28 ++++++++------- src/test/java/domain/DeckTest.java | 57 ++++++++++++++++++++++++------ 4 files changed, 79 insertions(+), 24 deletions(-) diff --git a/README.md b/README.md index 4bd44db07d0..f4a63e5472c 100644 --- a/README.md +++ b/README.md @@ -68,4 +68,4 @@ - 합계에 따라 1 또는 11로 계산 -[ ] 카드 넣기 \ No newline at end of file +[x] 카드 넣기 \ No newline at end of file diff --git a/src/main/java/domain/Card.java b/src/main/java/domain/Card.java index c1192b12223..30b65d06530 100644 --- a/src/main/java/domain/Card.java +++ b/src/main/java/domain/Card.java @@ -1,5 +1,7 @@ package domain; +import java.util.Objects; + public class Card { private final CardShape cardShape; private final CardContents cardContents; @@ -16,4 +18,18 @@ public CardContents getCardContents() { public boolean isAce() { return cardContents.getNumber().equals("A"); } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + Card card = (Card) o; + return cardShape == card.cardShape && cardContents == card.cardContents; + } + + @Override + public int hashCode() { + return Objects.hash(cardShape, cardContents); + } } diff --git a/src/main/java/domain/Deck.java b/src/main/java/domain/Deck.java index 4b1c34724b1..aeeaa9b5774 100644 --- a/src/main/java/domain/Deck.java +++ b/src/main/java/domain/Deck.java @@ -5,7 +5,6 @@ import java.util.List; public class Deck { - private static final int INIT_DECK_SIZE = 2; private static final int BUST_CRITERIA = 21; private final List cards; @@ -19,20 +18,21 @@ public static Deck createDeck(CardCreationStrategy strategy) { } public static Deck createParticipantDeck(Deck deck) { - return new Deck(deck.drawCard(INIT_DECK_SIZE)); + List cards = new ArrayList<>( + List.of( + deck.drawCard(), + deck.drawCard() + ) + ); + return new Deck(cards); } - public List drawCard(int count) { - - if (count > cards.size() || count < 1) { + public Card drawCard() { + if (cards.isEmpty()) { throw new IllegalArgumentException(ErrorMessage.DRAW_CARD_OUT_OF_RANGE.getMessage()); } - ArrayList selectedCards = new ArrayList<>(); - for (int i = 0; i < count; i++) { - selectedCards.add(cards.removeFirst()); - } - return selectedCards; + return cards.removeFirst(); } public boolean isBust() { @@ -46,6 +46,11 @@ public int calculateCardScoreSum() { return sumAce + sumExceptAce; } + public int addCard(Card card) { + this.cards.add(card); + return cards.size(); + } + private int calculateCardScoreSumExceptAce() { int sum = 0; for (Card card : cards) { @@ -54,8 +59,7 @@ private int calculateCardScoreSumExceptAce() { return sum; } - - + private int addCardScoreExceptAce(Card card, int sum) { if (!card.isAce()) { sum += card.getCardContents().getScore(); diff --git a/src/test/java/domain/DeckTest.java b/src/test/java/domain/DeckTest.java index 6c93e2ff183..c44c942bdf0 100644 --- a/src/test/java/domain/DeckTest.java +++ b/src/test/java/domain/DeckTest.java @@ -13,8 +13,6 @@ 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.ValueSource; class DeckTest { Deck deck; @@ -66,21 +64,58 @@ public List create() { assertThat(deck.calculateCardScoreSum()).isEqualTo(16); } + @Test + @DisplayName("덱에 카드 한장을 추가함") + void add_card() { + // given + CardCreationStrategy gameStrategy = new CardCreationStrategy() { + @Override + public List create() { + Card spadeJ = new Card(CardShape.스페이드, CardContents.J); + Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); + Card diamond3 = new Card(CardShape.다이아몬드, CardContents.THREE); + + return new ArrayList<>(List.of(spadeJ, clover5, diamond3)); + } + }; + Deck gameDeck = Deck.createDeck(gameStrategy); + + CardCreationStrategy playerStrategy = new CardCreationStrategy() { + @Override + public List create() { + Card spadeA = new Card(CardShape.스페이드, CardContents.A); + + return new ArrayList<>(List.of(spadeA)); + } + }; + Deck playerDeck = Deck.createDeck(playerStrategy); + + // when + int result = playerDeck.addCard(gameDeck.drawCard()); + int expected = 2; + + // then + assertThat(result).isEqualTo(expected); + } + @Nested class drawTest { - @ParameterizedTest - @ValueSource(ints = {1, 2}) - @DisplayName("Deck에서 카드를 원하는 장수만큼 뽑아줌") - void draw_card_success(int count) { - assertThat(deck.drawCard(count).size()).isEqualTo(count); + @Test + @DisplayName("Deck에서 카드를 한장 뽑아줌") + void draw_card_success() { + Card result = deck.drawCard(); + Card expected = new Card(CardShape.스페이드, CardContents.J); + assertThat(result).isEqualTo(expected); } - @ParameterizedTest - @ValueSource(ints = {0, 53}) + @Test @DisplayName("Deck에서 0이하 혹은 남은 카드 이상의 숫자 선택 시도 시 오류 발생") - void draw_card_fail(int count) { + void draw_card_fail() { + deck.drawCard(); + deck.drawCard(); + assertThatThrownBy( - () -> deck.drawCard(count) + () -> deck.drawCard() ).isInstanceOf(IllegalArgumentException.class) .hasMessage(ErrorMessage.DRAW_CARD_OUT_OF_RANGE.getMessage()); } From 200ce6c5e515d5fa2feb4c8a4034c265cfefb0bc Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 6 Mar 2026 01:16:39 +0900 Subject: [PATCH 023/129] feat: add conditional card drawing feature for dealer --- README.md | 2 +- src/main/java/domain/Dealer.java | 23 +++++++++++++++++++ src/test/java/domain/DealerTest.java | 34 ++++++++++++++++++++++++++++ 3 files changed, 58 insertions(+), 1 deletion(-) create mode 100644 src/main/java/domain/Dealer.java create mode 100644 src/test/java/domain/DealerTest.java diff --git a/README.md b/README.md index f4a63e5472c..4d22636ee02 100644 --- a/README.md +++ b/README.md @@ -32,7 +32,7 @@ ### Dealer -[ ] 카드를 받는다. +[x] 카드를 받는다. - 점수 합이 16 이하면 카드를 한장 더 받는다. diff --git a/src/main/java/domain/Dealer.java b/src/main/java/domain/Dealer.java new file mode 100644 index 00000000000..35ae938f2d0 --- /dev/null +++ b/src/main/java/domain/Dealer.java @@ -0,0 +1,23 @@ +package domain; + +import java.util.Optional; + +public class Dealer { + private static final int MINIMUM_TOTAL_SCORE = 16; + + private final Deck deck; + private final String name = "딜러"; + + public Dealer(Deck deck) { + this.deck = deck; + } + + public Optional addCardWhenSumBelowMinimum(Deck totalDeck) { + if (deck.calculateCardScoreSum() <= MINIMUM_TOTAL_SCORE) { + Card newCard = totalDeck.drawCard(); + deck.addCard(newCard); + return Optional.of(newCard); + } + return Optional.empty(); + } +} diff --git a/src/test/java/domain/DealerTest.java b/src/test/java/domain/DealerTest.java new file mode 100644 index 00000000000..310b547dc69 --- /dev/null +++ b/src/test/java/domain/DealerTest.java @@ -0,0 +1,34 @@ +package domain; + +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class DealerTest { + @Test + @DisplayName("딜러는 카드의 합이 16 이하면 카드를 한 장 더 받는다") + void addCardWhenSumBelowMinimum() { + //given + CardCreationStrategy dealerCardCreationStrategy = () -> { + Card spadeJ = new Card(CardShape.스페이드, CardContents.J); + return new ArrayList<>(List.of(spadeJ)); + }; + CardCreationStrategy totalCardCreationStrategy = () -> { + Card spadeA = new Card(CardShape.스페이드, CardContents.A); + Card heartA = new Card(CardShape.하트, CardContents.TWO); + return new ArrayList<>(List.of(spadeA, heartA)); + }; + Deck dealerDeck = Deck.createDeck(dealerCardCreationStrategy); + Deck totalDeck = Deck.createDeck(totalCardCreationStrategy); + Dealer dealer = new Dealer(dealerDeck); + + //when + Optional result = dealer.addCardWhenSumBelowMinimum(totalDeck); + + //then + Assertions.assertThat(result.isPresent()).isTrue(); + } +} From b79ef33647c153668d7aebd8bcde2e50355d1682 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 6 Mar 2026 10:59:50 +0900 Subject: [PATCH 024/129] refactor: change dealer constructor parameter name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 딜러 생성 시 필요한 파라미터 Deck의 인스턴스 종류를 명시함 --- src/main/java/domain/Dealer.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/domain/Dealer.java b/src/main/java/domain/Dealer.java index 35ae938f2d0..c58149f3f3f 100644 --- a/src/main/java/domain/Dealer.java +++ b/src/main/java/domain/Dealer.java @@ -4,12 +4,12 @@ public class Dealer { private static final int MINIMUM_TOTAL_SCORE = 16; - + private final Deck deck; private final String name = "딜러"; - public Dealer(Deck deck) { - this.deck = deck; + public Dealer(Deck participantDeck) { + this.deck = participantDeck; } public Optional addCardWhenSumBelowMinimum(Deck totalDeck) { From 160cc41b33cd117d8a573c0265c305c1c8ae7403 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 6 Mar 2026 11:05:10 +0900 Subject: [PATCH 025/129] test: add dealer constructor test --- src/test/java/domain/DealerTest.java | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/test/java/domain/DealerTest.java b/src/test/java/domain/DealerTest.java index 310b547dc69..1e66e3ffd8f 100644 --- a/src/test/java/domain/DealerTest.java +++ b/src/test/java/domain/DealerTest.java @@ -1,5 +1,7 @@ package domain; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -8,6 +10,25 @@ import org.junit.jupiter.api.Test; public class DealerTest { + @Test + @DisplayName("Dealer를 생성할 때 오류 발생 안함") + void dealer_create_success() { + CardCreationStrategy fixedCardCreationStrategy = new CardCreationStrategy() { + @Override + public List create() { + Card spadeJ = new Card(CardShape.스페이드, CardContents.J); + Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); + + return new ArrayList<>(List.of(spadeJ, clover5)); + } + }; + Deck deck = Deck.createDeck(fixedCardCreationStrategy); + Deck participantDeck = Deck.createParticipantDeck(deck); + assertDoesNotThrow( + () -> new Dealer(participantDeck) + ); + } + @Test @DisplayName("딜러는 카드의 합이 16 이하면 카드를 한 장 더 받는다") void addCardWhenSumBelowMinimum() { From 7721bca9ca199f464f5a223e9b9b47c7cdcc1c6d Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 6 Mar 2026 11:48:45 +0900 Subject: [PATCH 026/129] refactor : separate common logic between Dealer and Player MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 공통 로직을 제거하기 위해 participant 추가 --- src/main/java/domain/Dealer.java | 12 +++++------- src/main/java/domain/Participant.java | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 7 deletions(-) create mode 100644 src/main/java/domain/Participant.java diff --git a/src/main/java/domain/Dealer.java b/src/main/java/domain/Dealer.java index c58149f3f3f..5a3c55f80f9 100644 --- a/src/main/java/domain/Dealer.java +++ b/src/main/java/domain/Dealer.java @@ -2,20 +2,18 @@ import java.util.Optional; -public class Dealer { +public class Dealer extends Participant { private static final int MINIMUM_TOTAL_SCORE = 16; - - private final Deck deck; - private final String name = "딜러"; + private static final String DEALER_NAME = "딜러"; public Dealer(Deck participantDeck) { - this.deck = participantDeck; + super(participantDeck, DEALER_NAME); } public Optional addCardWhenSumBelowMinimum(Deck totalDeck) { - if (deck.calculateCardScoreSum() <= MINIMUM_TOTAL_SCORE) { + if (super.calculateDeckSum() <= MINIMUM_TOTAL_SCORE) { Card newCard = totalDeck.drawCard(); - deck.addCard(newCard); + super.addCard(newCard); return Optional.of(newCard); } return Optional.empty(); diff --git a/src/main/java/domain/Participant.java b/src/main/java/domain/Participant.java new file mode 100644 index 00000000000..9ad7400f4fd --- /dev/null +++ b/src/main/java/domain/Participant.java @@ -0,0 +1,23 @@ +package domain; + +public abstract class Participant { + private final Deck deck; + private final String name; + + protected Participant(Deck participantDeck, String name) { + this.deck = participantDeck; + this.name = name; + } + + public boolean isBust() { + return deck.isBust(); + } + + public int calculateDeckSum() { + return deck.calculateCardScoreSum(); + } + + public int addCard(Card card) { + return this.deck.addCard(card); + } +} From ddf4f941f56c56731554ea12afe989d5de6cdbf9 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 6 Mar 2026 17:27:13 +0900 Subject: [PATCH 027/129] fix : block call addCard method in Dealer Instance MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 딜러만의 고유한 카드 추가 로직을 위해 별도의 메서드가 선언되어 있습니다. 차후 다른 개발자가 addCard 메서드 호출 시 현재 설계상으로는 오류가 발생하기 때문에 UnsupportedOperationException을 던지는 것으로 override 해두었습니다 --- src/main/java/common/ErrorMessage.java | 3 ++- src/main/java/domain/Dealer.java | 11 +++++++++++ src/test/java/domain/DealerTest.java | 19 +++++++++++++++++++ 3 files changed, 32 insertions(+), 1 deletion(-) diff --git a/src/main/java/common/ErrorMessage.java b/src/main/java/common/ErrorMessage.java index 6bbf3b285d6..35ca02f50f7 100644 --- a/src/main/java/common/ErrorMessage.java +++ b/src/main/java/common/ErrorMessage.java @@ -2,7 +2,8 @@ public enum ErrorMessage { HIT_OR_STAND_VALUE_MIS_MATCH("y 혹은 n만 입력 가능합니다."), - DRAW_CARD_OUT_OF_RANGE("양수 이상의 숫자 중 남은 카드 수 만큼만 선택 가능"); + DRAW_CARD_OUT_OF_RANGE("양수 이상의 숫자 중 남은 카드 수 만큼만 선택 가능"), + UNSUPPORTED_OPERATION_MESSAGE("해당 객체[%s]에서는 지원하지 않는 메서드입니다 "); private final String message; diff --git a/src/main/java/domain/Dealer.java b/src/main/java/domain/Dealer.java index 5a3c55f80f9..3c53f2d49ff 100644 --- a/src/main/java/domain/Dealer.java +++ b/src/main/java/domain/Dealer.java @@ -1,5 +1,6 @@ package domain; +import common.ErrorMessage; import java.util.Optional; public class Dealer extends Participant { @@ -18,4 +19,14 @@ public Optional addCardWhenSumBelowMinimum(Deck totalDeck) { } return Optional.empty(); } + + @Override + public int addCard(Card card) { + throw new UnsupportedOperationException( + String.format( + ErrorMessage.UNSUPPORTED_OPERATION_MESSAGE.getMessage(), + this.getClass() + ) + ); + } } diff --git a/src/test/java/domain/DealerTest.java b/src/test/java/domain/DealerTest.java index 1e66e3ffd8f..54d0d9272dd 100644 --- a/src/test/java/domain/DealerTest.java +++ b/src/test/java/domain/DealerTest.java @@ -52,4 +52,23 @@ void addCardWhenSumBelowMinimum() { //then Assertions.assertThat(result.isPresent()).isTrue(); } + + @Test + @DisplayName("딜러에서 addCard 호출은 오류가 발생") + void call_addCard_in_Delaer_throw_error() { + CardCreationStrategy dealerCardCreationStrategy = () -> { + Card spadeJ = new Card(CardShape.스페이드, CardContents.J); + return new ArrayList<>(List.of(spadeJ)); + }; + + Deck dealerDeck = Deck.createDeck(dealerCardCreationStrategy); + Dealer dealer = new Dealer(dealerDeck); + String excpectExceptionMessage = "해당 객체[class domain.Dealer]에서는 지원하지 않는 메서드입니다 "; + + Assertions.assertThatThrownBy( + () -> dealer.addCard(new Card(CardShape.하트, CardContents.EIGHT)) + ).isInstanceOf(UnsupportedOperationException.class) + .hasMessage(excpectExceptionMessage) + ; + } } From f6ed1315fa7e53ad8fcaec128692fc540ada8a70 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 6 Mar 2026 17:27:50 +0900 Subject: [PATCH 028/129] fix : fix typo --- src/test/java/domain/DealerTest.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/test/java/domain/DealerTest.java b/src/test/java/domain/DealerTest.java index 54d0d9272dd..6313e12cb0e 100644 --- a/src/test/java/domain/DealerTest.java +++ b/src/test/java/domain/DealerTest.java @@ -55,7 +55,7 @@ void addCardWhenSumBelowMinimum() { @Test @DisplayName("딜러에서 addCard 호출은 오류가 발생") - void call_addCard_in_Delaer_throw_error() { + void call_addCard_in_Dealer_throw_error() { CardCreationStrategy dealerCardCreationStrategy = () -> { Card spadeJ = new Card(CardShape.스페이드, CardContents.J); return new ArrayList<>(List.of(spadeJ)); @@ -63,12 +63,12 @@ void call_addCard_in_Delaer_throw_error() { Deck dealerDeck = Deck.createDeck(dealerCardCreationStrategy); Dealer dealer = new Dealer(dealerDeck); - String excpectExceptionMessage = "해당 객체[class domain.Dealer]에서는 지원하지 않는 메서드입니다 "; + String expectExceptionMessage = "해당 객체[class domain.Dealer]에서는 지원하지 않는 메서드입니다 "; Assertions.assertThatThrownBy( () -> dealer.addCard(new Card(CardShape.하트, CardContents.EIGHT)) ).isInstanceOf(UnsupportedOperationException.class) - .hasMessage(excpectExceptionMessage) + .hasMessage(expectExceptionMessage) ; } } From 638445c906301b513cf53f8e19fd0da5b97b9417 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 6 Mar 2026 17:29:43 +0900 Subject: [PATCH 029/129] refactor: delete '\n' --- src/main/java/domain/Participant.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/domain/Participant.java b/src/main/java/domain/Participant.java index 9ad7400f4fd..c9430ce408d 100644 --- a/src/main/java/domain/Participant.java +++ b/src/main/java/domain/Participant.java @@ -20,4 +20,4 @@ public int calculateDeckSum() { public int addCard(Card card) { return this.deck.addCard(card); } -} +} \ No newline at end of file From 30ef159a9caec1573502b9fc0d1fbbfc99a642e4 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 6 Mar 2026 17:30:01 +0900 Subject: [PATCH 030/129] feat: add Player class --- src/main/java/domain/Player.java | 9 +++++ src/test/java/domain/PlayerTest.java | 57 ++++++++++++++++++++++++++++ 2 files changed, 66 insertions(+) create mode 100644 src/main/java/domain/Player.java create mode 100644 src/test/java/domain/PlayerTest.java diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java new file mode 100644 index 00000000000..429d215956b --- /dev/null +++ b/src/main/java/domain/Player.java @@ -0,0 +1,9 @@ +package domain; + +public class Player extends Participant { + + public Player(Deck participantDeck, String name) { + super(participantDeck, name); + } + +} diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java new file mode 100644 index 00000000000..525d49217e6 --- /dev/null +++ b/src/test/java/domain/PlayerTest.java @@ -0,0 +1,57 @@ +package domain; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import java.util.ArrayList; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class PlayerTest { + @Test + @DisplayName("Player를 생성할 때 오류 발생 안함") + void player_create_success() { + CardCreationStrategy fixedCardCreationStrategy = new CardCreationStrategy() { + @Override + public List create() { + Card spadeJ = new Card(CardShape.스페이드, CardContents.J); + Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); + + return new ArrayList<>(List.of(spadeJ, clover5)); + } + }; + Deck deck = Deck.createDeck(fixedCardCreationStrategy); + Deck participantDeck = Deck.createParticipantDeck(deck); + String name = "pobi"; + + assertDoesNotThrow( + () -> new Player(participantDeck, name) + ); + } + + @Test + @DisplayName("플레이어가 카드를 한 장 더 받는다") + void addCardWhenSumBelowMinimum() { + //given + CardCreationStrategy playerCardCreationStrategy = () -> { + Card spadeJ = new Card(CardShape.스페이드, CardContents.J); + return new ArrayList<>(List.of(spadeJ)); + }; + CardCreationStrategy totalCardCreationStrategy = () -> { + Card spadeA = new Card(CardShape.스페이드, CardContents.A); + Card heartA = new Card(CardShape.하트, CardContents.TWO); + return new ArrayList<>(List.of(spadeA, heartA)); + }; + Deck playerDeck = Deck.createDeck(playerCardCreationStrategy); + String testPlayerName = "pobi"; + Deck totalDeck = Deck.createDeck(totalCardCreationStrategy); + Player player = new Player(playerDeck, testPlayerName); + + //when + int result = player.addCard(totalDeck.drawCard()); + + //then + Assertions.assertThat(result).isEqualTo(2); + } +} \ No newline at end of file From 61377fcf277e5311b1db08622cd30669c87410b4 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Fri, 6 Mar 2026 17:30:20 +0900 Subject: [PATCH 031/129] docs: check what is implemented --- README.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 4d22636ee02..7a883ae4277 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,9 @@ ### Participant -[ ] 초기 카드 2장을 받는다. +[x] 버스트 판정 +[x] 총합 구하기 +[x] 카드 받기 ### Dealer @@ -38,7 +40,7 @@ ### Player -[ ] 카드를 받는다. +[x] 카드를 받는다. - `hit`를 선택하면 한 장 더 받는다. From 3790ad7406b424df4a914edcbc45277bc9841866 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 7 Mar 2026 14:09:52 +0900 Subject: [PATCH 032/129] refactor : change variable name to `totalDeck` MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 변수 deck의 모호성을 제거하기 위해 변경 --- src/main/java/domain/Deck.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/domain/Deck.java b/src/main/java/domain/Deck.java index aeeaa9b5774..5a65277c29d 100644 --- a/src/main/java/domain/Deck.java +++ b/src/main/java/domain/Deck.java @@ -17,11 +17,11 @@ public static Deck createDeck(CardCreationStrategy strategy) { return new Deck(cards); } - public static Deck createParticipantDeck(Deck deck) { + public static Deck createParticipantDeck(Deck totaldeck) { List cards = new ArrayList<>( List.of( - deck.drawCard(), - deck.drawCard() + totaldeck.drawCard(), + totaldeck.drawCard() ) ); return new Deck(cards); @@ -59,7 +59,7 @@ private int calculateCardScoreSumExceptAce() { return sum; } - + private int addCardScoreExceptAce(Card card, int sum) { if (!card.isAce()) { sum += card.getCardContents().getScore(); From ee6513697b4205fcc80db38407b6fe5909019e35 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 7 Mar 2026 14:12:51 +0900 Subject: [PATCH 033/129] fix : integrate adding card feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 딜러와 플레이어의 카드 추가 메서드를 단일화하여 차후 Participant 로 사용이 가능하도록 변경 --- src/main/java/domain/Dealer.java | 18 +++------------- src/main/java/domain/Participant.java | 8 +++++-- src/test/java/domain/DealerTest.java | 31 ++++++++++++++++----------- src/test/java/domain/PlayerTest.java | 8 +++---- 4 files changed, 32 insertions(+), 33 deletions(-) diff --git a/src/main/java/domain/Dealer.java b/src/main/java/domain/Dealer.java index 3c53f2d49ff..8a93954870b 100644 --- a/src/main/java/domain/Dealer.java +++ b/src/main/java/domain/Dealer.java @@ -1,6 +1,5 @@ package domain; -import common.ErrorMessage; import java.util.Optional; public class Dealer extends Participant { @@ -11,22 +10,11 @@ public Dealer(Deck participantDeck) { super(participantDeck, DEALER_NAME); } - public Optional addCardWhenSumBelowMinimum(Deck totalDeck) { + @Override + public Optional addCard(Deck totalDeck) { if (super.calculateDeckSum() <= MINIMUM_TOTAL_SCORE) { - Card newCard = totalDeck.drawCard(); - super.addCard(newCard); - return Optional.of(newCard); + return super.addCard(totalDeck); } return Optional.empty(); } - - @Override - public int addCard(Card card) { - throw new UnsupportedOperationException( - String.format( - ErrorMessage.UNSUPPORTED_OPERATION_MESSAGE.getMessage(), - this.getClass() - ) - ); - } } diff --git a/src/main/java/domain/Participant.java b/src/main/java/domain/Participant.java index c9430ce408d..7bc0c455812 100644 --- a/src/main/java/domain/Participant.java +++ b/src/main/java/domain/Participant.java @@ -1,5 +1,7 @@ package domain; +import java.util.Optional; + public abstract class Participant { private final Deck deck; private final String name; @@ -17,7 +19,9 @@ public int calculateDeckSum() { return deck.calculateCardScoreSum(); } - public int addCard(Card card) { - return this.deck.addCard(card); + public Optional addCard(Deck totalDeck) { + Card newCard = totalDeck.drawCard(); + this.deck.addCard(newCard); + return Optional.of(newCard); } } \ No newline at end of file diff --git a/src/test/java/domain/DealerTest.java b/src/test/java/domain/DealerTest.java index 6313e12cb0e..2491c86489e 100644 --- a/src/test/java/domain/DealerTest.java +++ b/src/test/java/domain/DealerTest.java @@ -31,7 +31,7 @@ public List create() { @Test @DisplayName("딜러는 카드의 합이 16 이하면 카드를 한 장 더 받는다") - void addCardWhenSumBelowMinimum() { + void addCard_Dealer_success() { //given CardCreationStrategy dealerCardCreationStrategy = () -> { Card spadeJ = new Card(CardShape.스페이드, CardContents.J); @@ -47,28 +47,35 @@ void addCardWhenSumBelowMinimum() { Dealer dealer = new Dealer(dealerDeck); //when - Optional result = dealer.addCardWhenSumBelowMinimum(totalDeck); + Optional result = dealer.addCard(totalDeck); //then Assertions.assertThat(result.isPresent()).isTrue(); } + @Test - @DisplayName("딜러에서 addCard 호출은 오류가 발생") - void call_addCard_in_Dealer_throw_error() { + @DisplayName("딜러는 카드의 합이 16 이상 -> Optional를 반환") + void addCard_Dealer_Optional_empty() { + //given CardCreationStrategy dealerCardCreationStrategy = () -> { Card spadeJ = new Card(CardShape.스페이드, CardContents.J); - return new ArrayList<>(List.of(spadeJ)); + Card heartJ = new Card(CardShape.하트, CardContents.J); + return new ArrayList<>(List.of(spadeJ, heartJ)); + }; + CardCreationStrategy totalCardCreationStrategy = () -> { + Card spadeA = new Card(CardShape.스페이드, CardContents.A); + Card heartA = new Card(CardShape.하트, CardContents.TWO); + return new ArrayList<>(List.of(spadeA, heartA)); }; - Deck dealerDeck = Deck.createDeck(dealerCardCreationStrategy); + Deck totalDeck = Deck.createDeck(totalCardCreationStrategy); Dealer dealer = new Dealer(dealerDeck); - String expectExceptionMessage = "해당 객체[class domain.Dealer]에서는 지원하지 않는 메서드입니다 "; - Assertions.assertThatThrownBy( - () -> dealer.addCard(new Card(CardShape.하트, CardContents.EIGHT)) - ).isInstanceOf(UnsupportedOperationException.class) - .hasMessage(expectExceptionMessage) - ; + //when + Optional result = dealer.addCard(totalDeck); + + //then + Assertions.assertThat(result.isPresent()).isFalse(); } } diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java index 525d49217e6..4b4e2978e49 100644 --- a/src/test/java/domain/PlayerTest.java +++ b/src/test/java/domain/PlayerTest.java @@ -34,14 +34,14 @@ public List create() { @DisplayName("플레이어가 카드를 한 장 더 받는다") void addCardWhenSumBelowMinimum() { //given + Card expectResultCard = new Card(CardShape.스페이드, CardContents.A); CardCreationStrategy playerCardCreationStrategy = () -> { Card spadeJ = new Card(CardShape.스페이드, CardContents.J); return new ArrayList<>(List.of(spadeJ)); }; CardCreationStrategy totalCardCreationStrategy = () -> { - Card spadeA = new Card(CardShape.스페이드, CardContents.A); Card heartA = new Card(CardShape.하트, CardContents.TWO); - return new ArrayList<>(List.of(spadeA, heartA)); + return new ArrayList<>(List.of(expectResultCard, heartA)); }; Deck playerDeck = Deck.createDeck(playerCardCreationStrategy); String testPlayerName = "pobi"; @@ -49,9 +49,9 @@ void addCardWhenSumBelowMinimum() { Player player = new Player(playerDeck, testPlayerName); //when - int result = player.addCard(totalDeck.drawCard()); + Card resultCard = player.addCard(totalDeck).get(); //then - Assertions.assertThat(result).isEqualTo(2); + Assertions.assertThat(resultCard).isEqualTo(expectResultCard); } } \ No newline at end of file From 08f1a5efefa137f0afb3d9edcdced72ba085391e Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 7 Mar 2026 14:19:14 +0900 Subject: [PATCH 034/129] feat : add createParticipants feature --- README.md | 14 +++++++- src/main/java/service/BlackJackService.java | 37 +++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 src/main/java/service/BlackJackService.java diff --git a/README.md b/README.md index 7a883ae4277..90f5f3c2e8d 100644 --- a/README.md +++ b/README.md @@ -44,6 +44,10 @@ - `hit`를 선택하면 한 장 더 받는다. +[ ] 플레이어의 게임 진행 상태를 결정한다. + +- Hit과 Stand 중 선택헤 자신의 게임 진행 상태를 결정한다. + ### Card [x] 카드 점수 계산 @@ -70,4 +74,12 @@ - 합계에 따라 1 또는 11로 계산 -[x] 카드 넣기 \ No newline at end of file +[x] 카드 넣기 + +## 서비스 + +### BlackJackService + +[x] 딜러와 플레이어를 생성한다. + +- 딜러와 플레이어에게 각각 전체 덱에서 카드를 뽑아 초기 덱을 지급한다. diff --git a/src/main/java/service/BlackJackService.java b/src/main/java/service/BlackJackService.java new file mode 100644 index 00000000000..e5324480c39 --- /dev/null +++ b/src/main/java/service/BlackJackService.java @@ -0,0 +1,37 @@ +package service; + +import domain.Dealer; +import domain.Deck; +import domain.Participant; +import domain.Player; +import java.util.ArrayList; +import java.util.List; + +public class BlackJackService { + private final Deck totalDeck; + private final List participants = new ArrayList<>(); + + public BlackJackService(Deck totalDeck) { + this.totalDeck = totalDeck; + } + + public void createParticipants(List playerNames) { + createDealer(); + createPlayer(playerNames); + } + + private void createDealer() { + Dealer newDealer = new Dealer( + Deck.createParticipantDeck(totalDeck) + ); + participants.add(newDealer); + } + + private void createPlayer(List playerNames) { + playerNames.forEach( + name -> participants.add( + new Player(Deck.createParticipantDeck(totalDeck), name) + ) + ); + } +} From a78de9bd9eaebe370c8522595b422efe0c0c36bf Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 7 Mar 2026 17:06:00 +0900 Subject: [PATCH 035/129] fix: refactor participant list to a first-class collection MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 클래스 내부에서 필드 멤버로 List 사용하는 것을 줄이기 위해 - 추후 참가자들의 전체 정보를 알아야만 계산할 수 있는 내용들을 다룰 클래스가 필요해서 --- README.md | 6 ++ src/main/java/domain/Deck.java | 19 +++++ src/main/java/domain/Participant.java | 8 +++ src/main/java/domain/Participants.java | 51 +++++++++++++ src/test/java/domain/ParticipantsTest.java | 84 ++++++++++++++++++++++ 5 files changed, 168 insertions(+) create mode 100644 src/main/java/domain/Participants.java create mode 100644 src/test/java/domain/ParticipantsTest.java diff --git a/README.md b/README.md index 90f5f3c2e8d..09a9f600396 100644 --- a/README.md +++ b/README.md @@ -26,6 +26,10 @@ ## 도메인 +### Participants + +[x] 딜러와 플레이어를 생성한다. + ### Participant [x] 버스트 판정 @@ -76,6 +80,8 @@ [x] 카드 넣기 +### Game + ## 서비스 ### BlackJackService diff --git a/src/main/java/domain/Deck.java b/src/main/java/domain/Deck.java index 5a65277c29d..f83e5d90311 100644 --- a/src/main/java/domain/Deck.java +++ b/src/main/java/domain/Deck.java @@ -3,6 +3,7 @@ import common.ErrorMessage; import java.util.ArrayList; import java.util.List; +import java.util.Objects; public class Deck { private static final int BUST_CRITERIA = 21; @@ -27,6 +28,10 @@ public static Deck createParticipantDeck(Deck totaldeck) { return new Deck(cards); } + public List getCards() { + return cards; + } + public Card drawCard() { if (cards.isEmpty()) { throw new IllegalArgumentException(ErrorMessage.DRAW_CARD_OUT_OF_RANGE.getMessage()); @@ -66,4 +71,18 @@ private int addCardScoreExceptAce(Card card, int sum) { } return sum; } + + @Override + public boolean equals(Object o) { + if (o == null || getClass() != o.getClass()) { + return false; + } + Deck deck = (Deck) o; + return Objects.equals(cards, deck.cards); + } + + @Override + public int hashCode() { + return Objects.hashCode(cards); + } } diff --git a/src/main/java/domain/Participant.java b/src/main/java/domain/Participant.java index 7bc0c455812..10b99a40dd0 100644 --- a/src/main/java/domain/Participant.java +++ b/src/main/java/domain/Participant.java @@ -11,6 +11,14 @@ protected Participant(Deck participantDeck, String name) { this.name = name; } + public Deck getDeck() { + return deck; + } + + public String getName() { + return name; + } + public boolean isBust() { return deck.isBust(); } diff --git a/src/main/java/domain/Participants.java b/src/main/java/domain/Participants.java new file mode 100644 index 00000000000..51b9d02e127 --- /dev/null +++ b/src/main/java/domain/Participants.java @@ -0,0 +1,51 @@ +package domain; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class Participants { + private final List participants; + + private Participants(List participants) { + this.participants = participants; + } + + public static Participants of(List playerNames, Deck totalDeck) { + return new Participants(createParticipants(playerNames, totalDeck)); + } + + private static List createParticipants(List playerNames, Deck totalDeck) { + List participants = new ArrayList<>(); + participants.add(createDealer(totalDeck)); + participants.addAll(createPlayer(playerNames, totalDeck)); + return participants; + } + + private static Dealer createDealer(Deck totalDeck) { + return new Dealer( + Deck.createParticipantDeck(totalDeck) + ); + } + + private static List createPlayer(List playerNames, Deck totalDeck) { + List players = new ArrayList<>(); + + playerNames.forEach( + name -> players.add( + new Player(Deck.createParticipantDeck(totalDeck), name) + ) + ); + + return players; + } + + public Map getDecksPerUser() { + Map decksPerUser = new LinkedHashMap<>(); + for (Participant participant : participants) { + decksPerUser.put(participant.getName(), participant.getDeck()); + } + return decksPerUser; + } +} diff --git a/src/test/java/domain/ParticipantsTest.java b/src/test/java/domain/ParticipantsTest.java new file mode 100644 index 00000000000..3d4fa803171 --- /dev/null +++ b/src/test/java/domain/ParticipantsTest.java @@ -0,0 +1,84 @@ +package domain; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ParticipantsTest { + + Deck totalDeck; + + @BeforeEach + void setUpTotalDeck() { + CardCreationStrategy totalCardCreationStrategy = this::createSampleCards; + totalDeck = Deck.createDeck(totalCardCreationStrategy); + } + + @Test + @DisplayName("생성 잘 한다") + void of_good() { + //given + List testPlayerNames = List.of("pobi", "terry", "rati", "gump"); + + //when, then + assertDoesNotThrow( + () -> Participants.of(testPlayerNames, totalDeck) + ); + } + + @Test + @DisplayName("getDecksPerUser에서 잘 가져온다") + void getDecksPerUser_success() { + //given + List testPlayerNames = List.of("pobi"); + + CardCreationStrategy dealerCardStrategy = () -> { + return new ArrayList<>( + List.of( + new Card(CardShape.스페이드, CardContents.A), + new Card(CardShape.스페이드, CardContents.TWO) + ) + ); + }; + CardCreationStrategy pobiCardStrategy = () -> { + return new ArrayList<>( + List.of( + new Card(CardShape.스페이드, CardContents.THREE), + new Card(CardShape.스페이드, CardContents.FOUR) + ) + ); + }; + Deck expectDealerCardDeck = Deck.createDeck(dealerCardStrategy); + Deck expectPobiCardDeck = Deck.createDeck(pobiCardStrategy); + + //when + Participants participants = Participants.of(testPlayerNames, totalDeck); + Map result = participants.getDecksPerUser(); + + //then + + assertEquals(expectDealerCardDeck, result.get("딜러")); + assertEquals(expectPobiCardDeck, result.get("pobi")); + } + + private List createSampleCards() { + CardShape[] shapes = CardShape.values(); + CardContents[] contents = CardContents.values(); + + List sampleCards = new ArrayList<>(); + for (CardShape cardShape : shapes) { + for (CardContents content : contents) { + sampleCards.add(new Card(cardShape, content)); + } + } + + return sampleCards; + } + +} \ No newline at end of file From 627bee8a9a621ab5caf0980cd0c2ab052c4e99a1 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 7 Mar 2026 17:18:08 +0900 Subject: [PATCH 036/129] feat: add game creation feature --- README.md | 6 +++++ src/main/java/domain/Game.java | 20 ++++++++++++++++ src/test/java/domain/GameTest.java | 37 ++++++++++++++++++++++++++++++ 3 files changed, 63 insertions(+) create mode 100644 src/main/java/domain/Game.java create mode 100644 src/test/java/domain/GameTest.java diff --git a/README.md b/README.md index 09a9f600396..5c1e51dd9ea 100644 --- a/README.md +++ b/README.md @@ -82,6 +82,12 @@ ### Game +[x] 게임에 필요한 객체들을 생성한다. + +- 전체 덱 객체를 생성한다. +- 참가자들의 일급 컬렉션 객체를 생성한다. + - 참가자들에는 딜러와 플레이어들이 포함된다. + ## 서비스 ### BlackJackService diff --git a/src/main/java/domain/Game.java b/src/main/java/domain/Game.java new file mode 100644 index 00000000000..dac17e70c81 --- /dev/null +++ b/src/main/java/domain/Game.java @@ -0,0 +1,20 @@ +package domain; + +import java.util.List; + +public class Game { + private final Deck totalDeck; + private final Participants participants; + + public Game(Deck totalDeck, Participants participants) { + this.totalDeck = totalDeck; + this.participants = participants; + } + + public static Game ready(List playerNames, CardCreationStrategy strategy) { + Deck totalDeck = Deck.createDeck(strategy); + Participants participants = Participants.of(playerNames, totalDeck); + return new Game(totalDeck, participants); + } + +} diff --git a/src/test/java/domain/GameTest.java b/src/test/java/domain/GameTest.java new file mode 100644 index 00000000000..a60802a9d04 --- /dev/null +++ b/src/test/java/domain/GameTest.java @@ -0,0 +1,37 @@ +package domain; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import java.util.ArrayList; +import java.util.List; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class GameTest { + @Test + @DisplayName("생성 잘 한다") + void ready_good() { + //given + List testPlayerNames = List.of("pobi", "terry", "rati", "gump"); + CardCreationStrategy totalCardCreationStrategy = this::createSampleCards; + + //when, then + assertDoesNotThrow( + () -> Game.ready(testPlayerNames, totalCardCreationStrategy) + ); + } + + private List createSampleCards() { + CardShape[] shapes = CardShape.values(); + CardContents[] contents = CardContents.values(); + + List sampleCards = new ArrayList<>(); + for (CardShape cardShape : shapes) { + for (CardContents content : contents) { + sampleCards.add(new Card(cardShape, content)); + } + } + + return sampleCards; + } +} \ No newline at end of file From b0a7be853dafd9609e8b164e001c8c04915f3bc1 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 7 Mar 2026 18:09:41 +0900 Subject: [PATCH 037/129] refactor : change user input method to Scanner MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit BufferedReader 사용 시 IOException 관련 문제가 존재. 해당 문제를 핸들링 하는 것 보다 속도가 다소 느리더라도 Scanner 을 이용하여 문제를 핸들링 하지 않게 되는 것이 더 좋다고 판단하여 변경함 --- src/main/java/view/InputView.java | 27 +++++++++++++++------------ 1 file changed, 15 insertions(+), 12 deletions(-) diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 8a9bc726b6a..0c6d743846b 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,29 +1,32 @@ package view; import common.ErrorMessage; -import java.io.BufferedReader; -import java.io.IOException; -import java.io.InputStreamReader; import java.util.Arrays; import java.util.List; +import java.util.Scanner; public class InputView { private static final String DELIMITER = ","; private static final List HIT_OR_STAND = List.of("y", "n"); + private static final Scanner sc = new Scanner(System.in); - public List readNames() throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - - return Arrays.stream(br.readLine().split(DELIMITER)).toList(); + public List readNames() { + String line = sc.nextLine(); + validateIsBlank(line); + return Arrays.stream(line.split(DELIMITER)).toList(); } - public String readHitOrStand() throws IOException { - BufferedReader br = new BufferedReader(new InputStreamReader(System.in)); - String input = br.readLine().trim(); - + public String readHitOrStand() { + String input = sc.nextLine().trim(); + validateIsBlank(input); validateHitOrStandValue(input); + return input; + } - return br.readLine().trim(); + private void validateIsBlank(String line) { + if (line.isBlank()) { + throw new IllegalArgumentException(ErrorMessage.NOT_ALLOW_EMPTY_INPUT.getMessage()); + } } private void validateHitOrStandValue(String input) { From 9e65f06f5b7f47b8799055896df5d766190e9495 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 7 Mar 2026 18:10:39 +0900 Subject: [PATCH 038/129] feat : add constraint to Participants number --- src/main/java/common/ErrorMessage.java | 4 +++- src/main/java/domain/Participants.java | 5 +++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/common/ErrorMessage.java b/src/main/java/common/ErrorMessage.java index 35ca02f50f7..efb4f4d203c 100644 --- a/src/main/java/common/ErrorMessage.java +++ b/src/main/java/common/ErrorMessage.java @@ -3,7 +3,9 @@ public enum ErrorMessage { HIT_OR_STAND_VALUE_MIS_MATCH("y 혹은 n만 입력 가능합니다."), DRAW_CARD_OUT_OF_RANGE("양수 이상의 숫자 중 남은 카드 수 만큼만 선택 가능"), - UNSUPPORTED_OPERATION_MESSAGE("해당 객체[%s]에서는 지원하지 않는 메서드입니다 "); + UNSUPPORTED_OPERATION_MESSAGE("해당 객체[%s]에서는 지원하지 않는 메서드입니다 "), + NOT_ALLOW_EMPTY_INPUT("공백은 허용되지 않습니다"), + MAX_PLAYER_ERROR("최대 인원을 초과했습니다."); private final String message; diff --git a/src/main/java/domain/Participants.java b/src/main/java/domain/Participants.java index 51b9d02e127..4213b31d71b 100644 --- a/src/main/java/domain/Participants.java +++ b/src/main/java/domain/Participants.java @@ -1,5 +1,6 @@ package domain; +import common.ErrorMessage; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.List; @@ -30,6 +31,10 @@ private static Dealer createDealer(Deck totalDeck) { } private static List createPlayer(List playerNames, Deck totalDeck) { + if (playerNames.size() > 5) { + throw new IllegalArgumentException(ErrorMessage.MAX_PLAYER_ERROR.getMessage()); + } + List players = new ArrayList<>(); playerNames.forEach( From 00e8662c20aaf92b69eb0a00186f7fff50f287bc Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 7 Mar 2026 18:11:29 +0900 Subject: [PATCH 039/129] build : add mockito for test logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 테스트 로직 작성의 용이성을 위해 mockito 추가 --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index ce846f70cc6..2b642d1a96d 100644 --- a/build.gradle +++ b/build.gradle @@ -13,6 +13,8 @@ dependencies { testImplementation platform('org.assertj:assertj-bom:3.27.3') testImplementation('org.junit.jupiter:junit-jupiter') testImplementation('org.assertj:assertj-core') + testImplementation 'org.mockito:mockito-core:4.3.1' + testImplementation 'org.mockito:mockito-junit-jupiter:4.3.1' } java { From ec4784b56bac58b1368a7af8040a31cba1011a0c Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 7 Mar 2026 20:22:19 +0900 Subject: [PATCH 040/129] feat: add initial card share detail printing feature --- README.md | 2 +- src/main/java/domain/Card.java | 5 ++ src/main/java/domain/Deck.java | 15 ------ src/main/java/domain/Game.java | 9 ++++ src/main/java/domain/Participants.java | 9 ++-- src/main/java/service/BlackJackService.java | 32 +++---------- src/main/java/view/OutputView.java | 53 ++++++++++++++++++++- src/test/java/domain/CardTest.java | 12 +++++ src/test/java/domain/ParticipantsTest.java | 34 +++++-------- 9 files changed, 103 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index 5c1e51dd9ea..3f76600963a 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ [x] 1. 게임 안내 문구를 출력한다. -[ ] 2. 초기 카드 내역을 출력한다. +[x] 2. 초기 카드 내역을 출력한다. [ ] 3. 최종 카드 내역을 출력한다. diff --git a/src/main/java/domain/Card.java b/src/main/java/domain/Card.java index 30b65d06530..2b9e4e5c352 100644 --- a/src/main/java/domain/Card.java +++ b/src/main/java/domain/Card.java @@ -32,4 +32,9 @@ public boolean equals(Object o) { public int hashCode() { return Objects.hash(cardShape, cardContents); } + + @Override + public String toString() { + return cardContents.getNumber() + cardShape.name(); + } } diff --git a/src/main/java/domain/Deck.java b/src/main/java/domain/Deck.java index f83e5d90311..71e472624a1 100644 --- a/src/main/java/domain/Deck.java +++ b/src/main/java/domain/Deck.java @@ -3,7 +3,6 @@ import common.ErrorMessage; import java.util.ArrayList; import java.util.List; -import java.util.Objects; public class Deck { private static final int BUST_CRITERIA = 21; @@ -71,18 +70,4 @@ private int addCardScoreExceptAce(Card card, int sum) { } return sum; } - - @Override - public boolean equals(Object o) { - if (o == null || getClass() != o.getClass()) { - return false; - } - Deck deck = (Deck) o; - return Objects.equals(cards, deck.cards); - } - - @Override - public int hashCode() { - return Objects.hashCode(cards); - } } diff --git a/src/main/java/domain/Game.java b/src/main/java/domain/Game.java index dac17e70c81..01bb3a0b017 100644 --- a/src/main/java/domain/Game.java +++ b/src/main/java/domain/Game.java @@ -1,8 +1,11 @@ package domain; import java.util.List; +import java.util.Map; public class Game { + private static final String DEALER = "딜러"; + private final Deck totalDeck; private final Participants participants; @@ -17,4 +20,10 @@ public static Game ready(List playerNames, CardCreationStrategy strategy return new Game(totalDeck, participants); } + public Map> showInitialCardShareResult() { + Map> result = participants.getDecksPerUser(); + List dealerCards = result.get(DEALER); + dealerCards.removeLast(); + return participants.getDecksPerUser(); + } } diff --git a/src/main/java/domain/Participants.java b/src/main/java/domain/Participants.java index 4213b31d71b..ad17f5bfbe3 100644 --- a/src/main/java/domain/Participants.java +++ b/src/main/java/domain/Participants.java @@ -46,10 +46,13 @@ private static List createPlayer(List playerNames, Deck totalDec return players; } - public Map getDecksPerUser() { - Map decksPerUser = new LinkedHashMap<>(); + public Map> getDecksPerUser() { + Map> decksPerUser = new LinkedHashMap<>(); for (Participant participant : participants) { - decksPerUser.put(participant.getName(), participant.getDeck()); + decksPerUser.put( + participant.getName(), + participant.getDeck().getCards() + ); } return decksPerUser; } diff --git a/src/main/java/service/BlackJackService.java b/src/main/java/service/BlackJackService.java index e5324480c39..d0072f332f8 100644 --- a/src/main/java/service/BlackJackService.java +++ b/src/main/java/service/BlackJackService.java @@ -1,37 +1,19 @@ package service; -import domain.Dealer; -import domain.Deck; -import domain.Participant; -import domain.Player; -import java.util.ArrayList; +import domain.CardCreationStrategy; +import domain.Game; import java.util.List; public class BlackJackService { - private final Deck totalDeck; - private final List participants = new ArrayList<>(); + private final CardCreationStrategy strategy; - public BlackJackService(Deck totalDeck) { - this.totalDeck = totalDeck; + public BlackJackService(CardCreationStrategy strategy) { + this.strategy = strategy; } - public void createParticipants(List playerNames) { - createDealer(); - createPlayer(playerNames); + public Game prepareGame(List playerNames) { + return Game.ready(playerNames, strategy); } - private void createDealer() { - Dealer newDealer = new Dealer( - Deck.createParticipantDeck(totalDeck) - ); - participants.add(newDealer); - } - private void createPlayer(List playerNames) { - playerNames.forEach( - name -> participants.add( - new Player(Deck.createParticipantDeck(totalDeck), name) - ) - ); - } } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index fb847531549..a8b8ce0f911 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,8 +1,13 @@ package view; +import domain.Card; +import domain.Participant; +import java.util.ArrayList; import java.util.List; +import java.util.Map; public class OutputView { + private static final String DEALER = "딜러"; private static final String NAME_PROMPT = "게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)"; private static final String INITIAL_CARD_SHARE = "딜러와 %s에게 2장을 나누었습니다.\n"; private static final String HIT_OR_STAND_PROMPT = "%s는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)\n"; @@ -12,8 +17,48 @@ public void printNamePrompt() { System.out.println(NAME_PROMPT); } - public void printInitialCardShare(List names) { - System.out.printf(INITIAL_CARD_SHARE, String.join(", ", names)); + public void printInitialCardShare(List playerNames) { + System.out.printf(INITIAL_CARD_SHARE, String.join(", ", playerNames)); + } + + public void printInitialCardShareDetail(Map> results) { + List playersNames = extractPlayersNames(results); + + StringBuilder initialCardShareDetail = new StringBuilder(); + + List dealerCards = results.remove(DEALER); + initialCardShareDetail.append(consistCardInfo(DEALER, dealerCards)); + + playersNames.forEach( + name -> { + List cards = results.get(name); + initialCardShareDetail.append(consistCardInfo(name, cards)); + } + ); + System.out.println(initialCardShareDetail); + } + + private String consistCardInfo(String name, List cards) { + List cardInfos = new ArrayList<>(); + for (Card card : cards) { + cardInfos.add(card.toString()); + } + return String.format("%s: %s\n", name, String.join(", ", cardInfos)); + } + + private List extractPlayersNames(Map> results) { + List names = new java.util.ArrayList<>(results.keySet().stream().toList()); + names.remove(DEALER); + return names; + } + + private String extractAndBuildPlayersName(List participants) { + StringBuilder sb = new StringBuilder(); + for (int i = 1; i < participants.size(); i++) { + sb.append(participants.get(i).getName()).append(","); + } + sb.deleteCharAt(sb.length() - 1); + return sb.toString(); } public void printHitOrStandPrompt(String name) { @@ -23,6 +68,10 @@ public void printHitOrStandPrompt(String name) { public void printAdditionalCardForDealerDescription() { System.out.println(ADDITIONAL_CARD_FOR_DEALER_DESCRIPTION); } + + public void printErrorMessage(Exception e) { + System.out.println(e.getMessage()); + } } //게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리) diff --git a/src/test/java/domain/CardTest.java b/src/test/java/domain/CardTest.java index 3f8caa6c922..815aa5c4427 100644 --- a/src/test/java/domain/CardTest.java +++ b/src/test/java/domain/CardTest.java @@ -23,6 +23,18 @@ void getCardContents_success() { Assertions.assertThat(result).isEqualTo(testCardContents); } + @Test + @DisplayName("카드 정보 출력 양식") + void card_toString() { + //given + CardContents testCardContents = CardContents.J; + Card testCard = new Card(CardShape.스페이드, testCardContents); + String expect = "J스페이드"; + + //when & then + Assertions.assertThat(testCard.toString()).isEqualTo(expect); + } + @Nested class isAceTest { @Test diff --git a/src/test/java/domain/ParticipantsTest.java b/src/test/java/domain/ParticipantsTest.java index 3d4fa803171..c06e801a10e 100644 --- a/src/test/java/domain/ParticipantsTest.java +++ b/src/test/java/domain/ParticipantsTest.java @@ -38,33 +38,23 @@ void getDecksPerUser_success() { //given List testPlayerNames = List.of("pobi"); - CardCreationStrategy dealerCardStrategy = () -> { - return new ArrayList<>( - List.of( - new Card(CardShape.스페이드, CardContents.A), - new Card(CardShape.스페이드, CardContents.TWO) - ) - ); - }; - CardCreationStrategy pobiCardStrategy = () -> { - return new ArrayList<>( - List.of( - new Card(CardShape.스페이드, CardContents.THREE), - new Card(CardShape.스페이드, CardContents.FOUR) - ) - ); - }; - Deck expectDealerCardDeck = Deck.createDeck(dealerCardStrategy); - Deck expectPobiCardDeck = Deck.createDeck(pobiCardStrategy); + List expectDealerCards = List.of( + new Card(CardShape.스페이드, CardContents.A), + new Card(CardShape.스페이드, CardContents.TWO) + ); + + List expectPobiCards = List.of( + new Card(CardShape.스페이드, CardContents.THREE), + new Card(CardShape.스페이드, CardContents.FOUR) + ); //when Participants participants = Participants.of(testPlayerNames, totalDeck); - Map result = participants.getDecksPerUser(); + Map> result = participants.getDecksPerUser(); //then - - assertEquals(expectDealerCardDeck, result.get("딜러")); - assertEquals(expectPobiCardDeck, result.get("pobi")); + assertEquals(expectDealerCards, result.get("딜러")); + assertEquals(expectPobiCards, result.get("pobi")); } private List createSampleCards() { From b6ea4953a8d6ff8afbe72bf3a186dc2dee4958f1 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sat, 7 Mar 2026 22:18:03 +0900 Subject: [PATCH 041/129] refactor: replace Participants with Players and restructure Game MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 부모 클래스(Participants)가 자식 객체를 생성하는 구조적 모순을 해결하기 위해 해당 공통 클래스 제거 - 다수의 플레이어를 관리하는 일급 컬렉션 Players 클래스 신규 추가 - Game 도메인이 Dealer와 Players를 각각 독립적인 필드로 분리하여 관리하도록 객체 구조 개선 - 도메인 구조 변경에 따라 Dealer에 getCards() 메서드를 추가하고, OutputView 출력 로직 수정 --- README.md | 8 +-- src/main/java/domain/Dealer.java | 5 ++ src/main/java/domain/Game.java | 34 +++++++--- src/main/java/domain/Participants.java | 59 ----------------- src/main/java/domain/Players.java | 63 +++++++++++++++++++ src/main/java/view/OutputView.java | 31 +++------ src/test/java/domain/GameTest.java | 29 +++++++++ ...ParticipantsTest.java => PlayersTest.java} | 19 ++---- 8 files changed, 139 insertions(+), 109 deletions(-) delete mode 100644 src/main/java/domain/Participants.java create mode 100644 src/main/java/domain/Players.java rename src/test/java/domain/{ParticipantsTest.java => PlayersTest.java} (76%) diff --git a/README.md b/README.md index 3f76600963a..287c99c761f 100644 --- a/README.md +++ b/README.md @@ -26,9 +26,9 @@ ## 도메인 -### Participants +### Players -[x] 딜러와 플레이어를 생성한다. +[x] 플레이어들을 생성한다. ### Participant @@ -85,8 +85,8 @@ [x] 게임에 필요한 객체들을 생성한다. - 전체 덱 객체를 생성한다. -- 참가자들의 일급 컬렉션 객체를 생성한다. - - 참가자들에는 딜러와 플레이어들이 포함된다. +- 딜러 객체를 생성한다. +- 플레이어 객체들을 생성한다. ## 서비스 diff --git a/src/main/java/domain/Dealer.java b/src/main/java/domain/Dealer.java index 8a93954870b..aeeae217154 100644 --- a/src/main/java/domain/Dealer.java +++ b/src/main/java/domain/Dealer.java @@ -1,5 +1,6 @@ package domain; +import java.util.List; import java.util.Optional; public class Dealer extends Participant { @@ -17,4 +18,8 @@ public Optional addCard(Deck totalDeck) { } return Optional.empty(); } + + public List getCards() { + return getDeck().getCards(); + } } diff --git a/src/main/java/domain/Game.java b/src/main/java/domain/Game.java index 01bb3a0b017..12d6a047c7c 100644 --- a/src/main/java/domain/Game.java +++ b/src/main/java/domain/Game.java @@ -7,23 +7,39 @@ public class Game { private static final String DEALER = "딜러"; private final Deck totalDeck; - private final Participants participants; + private final Dealer dealer; + private final Players players; - public Game(Deck totalDeck, Participants participants) { + public Game(Deck totalDeck, Dealer dealer, Players players) { this.totalDeck = totalDeck; - this.participants = participants; + this.dealer = dealer; + this.players = players; } public static Game ready(List playerNames, CardCreationStrategy strategy) { Deck totalDeck = Deck.createDeck(strategy); - Participants participants = Participants.of(playerNames, totalDeck); - return new Game(totalDeck, participants); + + Deck dealerDeck = Deck.createParticipantDeck(totalDeck); + Dealer dealer = new Dealer(dealerDeck); + + Players players = Players.of(playerNames, totalDeck); + + return new Game(totalDeck, dealer, players); } public Map> showInitialCardShareResult() { - Map> result = participants.getDecksPerUser(); - List dealerCards = result.get(DEALER); - dealerCards.removeLast(); - return participants.getDecksPerUser(); + Map> result = players.getDecksPerPlayer(); + + List dealerCard = dealer.getCards(); + dealerCard.removeLast(); + + result.put(DEALER, dealerCard); + + return result; + } + + public void play() { + //한 턴만. -> isAnyOneGo 안씀 + } } diff --git a/src/main/java/domain/Participants.java b/src/main/java/domain/Participants.java deleted file mode 100644 index ad17f5bfbe3..00000000000 --- a/src/main/java/domain/Participants.java +++ /dev/null @@ -1,59 +0,0 @@ -package domain; - -import common.ErrorMessage; -import java.util.ArrayList; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public class Participants { - private final List participants; - - private Participants(List participants) { - this.participants = participants; - } - - public static Participants of(List playerNames, Deck totalDeck) { - return new Participants(createParticipants(playerNames, totalDeck)); - } - - private static List createParticipants(List playerNames, Deck totalDeck) { - List participants = new ArrayList<>(); - participants.add(createDealer(totalDeck)); - participants.addAll(createPlayer(playerNames, totalDeck)); - return participants; - } - - private static Dealer createDealer(Deck totalDeck) { - return new Dealer( - Deck.createParticipantDeck(totalDeck) - ); - } - - private static List createPlayer(List playerNames, Deck totalDeck) { - if (playerNames.size() > 5) { - throw new IllegalArgumentException(ErrorMessage.MAX_PLAYER_ERROR.getMessage()); - } - - List players = new ArrayList<>(); - - playerNames.forEach( - name -> players.add( - new Player(Deck.createParticipantDeck(totalDeck), name) - ) - ); - - return players; - } - - public Map> getDecksPerUser() { - Map> decksPerUser = new LinkedHashMap<>(); - for (Participant participant : participants) { - decksPerUser.put( - participant.getName(), - participant.getDeck().getCards() - ); - } - return decksPerUser; - } -} diff --git a/src/main/java/domain/Players.java b/src/main/java/domain/Players.java new file mode 100644 index 00000000000..7036c12288e --- /dev/null +++ b/src/main/java/domain/Players.java @@ -0,0 +1,63 @@ +package domain; + +import common.ErrorMessage; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; + +public class Players { + private static final int MAX_PLAYER_NUMBER = 5; + + private final List players; + + private Players(List players) { + this.players = players; + } + + public static Players of(List playerNames, Deck totalDeck) { + return new Players(createPlayers(playerNames, totalDeck)); + } + + private static List createPlayers(List playerNames, Deck totalDeck) { + if (playerNames.size() > MAX_PLAYER_NUMBER) { + throw new IllegalArgumentException(ErrorMessage.MAX_PLAYER_ERROR.getMessage()); + } + + List players = new ArrayList<>(); + playerNames.forEach( + name -> players.add( + new Player(Deck.createParticipantDeck(totalDeck), name) + ) + ); + + return players; + } + + public Map> getDecksPerPlayer() { + Map> decksPerUser = new LinkedHashMap<>(); + for (Player player : players) { + decksPerUser.put( + player.getName(), + player.getDeck().getCards() + ); + } + return decksPerUser; + } + +// public List getPlayersNames() { +// +// } + + public boolean isAnyOneGo() { + boolean result = false; + + for (Player player : players) { + if (player.isGo()) { + result = true; + } + } + + return result; + } +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index a8b8ce0f911..f0c5576fcd0 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,10 +1,10 @@ package view; import domain.Card; -import domain.Participant; import java.util.ArrayList; import java.util.List; import java.util.Map; +import java.util.Map.Entry; public class OutputView { private static final String DEALER = "딜러"; @@ -22,19 +22,17 @@ public void printInitialCardShare(List playerNames) { } public void printInitialCardShareDetail(Map> results) { - List playersNames = extractPlayersNames(results); - StringBuilder initialCardShareDetail = new StringBuilder(); List dealerCards = results.remove(DEALER); initialCardShareDetail.append(consistCardInfo(DEALER, dealerCards)); - playersNames.forEach( - name -> { - List cards = results.get(name); - initialCardShareDetail.append(consistCardInfo(name, cards)); - } - ); + for (Entry> playersCardInfo : results.entrySet()) { + String playerName = playersCardInfo.getKey(); + List playerCards = playersCardInfo.getValue(); + initialCardShareDetail.append(consistCardInfo(playerName, playerCards)); + } + System.out.println(initialCardShareDetail); } @@ -46,21 +44,6 @@ private String consistCardInfo(String name, List cards) { return String.format("%s: %s\n", name, String.join(", ", cardInfos)); } - private List extractPlayersNames(Map> results) { - List names = new java.util.ArrayList<>(results.keySet().stream().toList()); - names.remove(DEALER); - return names; - } - - private String extractAndBuildPlayersName(List participants) { - StringBuilder sb = new StringBuilder(); - for (int i = 1; i < participants.size(); i++) { - sb.append(participants.get(i).getName()).append(","); - } - sb.deleteCharAt(sb.length() - 1); - return sb.toString(); - } - public void printHitOrStandPrompt(String name) { System.out.printf(HIT_OR_STAND_PROMPT, name); } diff --git a/src/test/java/domain/GameTest.java b/src/test/java/domain/GameTest.java index a60802a9d04..1e8ec8b97f0 100644 --- a/src/test/java/domain/GameTest.java +++ b/src/test/java/domain/GameTest.java @@ -1,9 +1,12 @@ package domain; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import java.util.ArrayList; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -21,6 +24,32 @@ void ready_good() { ); } + @Test + @DisplayName("초기 카드 배분 시 딜러는 1장, 플레이어는 2장씩 출력되어야 한다") + void showInitialCardShareResult_success() { + // given + List testPlayerNames = List.of("pobi", "terry", "rati", "gump"); + Game game = Game.ready(testPlayerNames, this::createSampleCards); + + // when + Map> result = game.showInitialCardShareResult(); + + // then + assertThat(result.get("딜러").size()).isEqualTo(1); + + testPlayerNames.forEach(name -> + assertThat(result.get(name).size()).isEqualTo(2) + ); + + System.out.println("========= 초기 배분 결과 ========="); + result.forEach((name, cards) -> { + String cardInfo = cards.stream() + .map(Card::toString) + .collect(Collectors.joining(", ")); + System.out.printf("%s : [ %s ]\n", name, cardInfo); + }); + } + private List createSampleCards() { CardShape[] shapes = CardShape.values(); CardContents[] contents = CardContents.values(); diff --git a/src/test/java/domain/ParticipantsTest.java b/src/test/java/domain/PlayersTest.java similarity index 76% rename from src/test/java/domain/ParticipantsTest.java rename to src/test/java/domain/PlayersTest.java index c06e801a10e..1e6f10b64ea 100644 --- a/src/test/java/domain/ParticipantsTest.java +++ b/src/test/java/domain/PlayersTest.java @@ -10,7 +10,7 @@ import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -class ParticipantsTest { +class PlayersTest { Deck totalDeck; @@ -28,7 +28,7 @@ void of_good() { //when, then assertDoesNotThrow( - () -> Participants.of(testPlayerNames, totalDeck) + () -> Players.of(testPlayerNames, totalDeck) ); } @@ -37,23 +37,16 @@ void of_good() { void getDecksPerUser_success() { //given List testPlayerNames = List.of("pobi"); - - List expectDealerCards = List.of( + List expectPobiCards = List.of( new Card(CardShape.스페이드, CardContents.A), new Card(CardShape.스페이드, CardContents.TWO) ); - List expectPobiCards = List.of( - new Card(CardShape.스페이드, CardContents.THREE), - new Card(CardShape.스페이드, CardContents.FOUR) - ); - //when - Participants participants = Participants.of(testPlayerNames, totalDeck); - Map> result = participants.getDecksPerUser(); + Players players = Players.of(testPlayerNames, totalDeck); + Map> result = players.getDecksPerPlayer(); //then - assertEquals(expectDealerCards, result.get("딜러")); assertEquals(expectPobiCards, result.get("pobi")); } @@ -71,4 +64,4 @@ private List createSampleCards() { return sampleCards; } -} \ No newline at end of file +} From 44e10cf6134962542d610ffcda8323ee428ea77a Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sun, 8 Mar 2026 18:49:31 +0900 Subject: [PATCH 042/129] feat: add name validation feature --- README.md | 3 +++ src/main/java/common/ErrorMessage.java | 1 + src/main/java/domain/Participant.java | 20 ++++++++++++++++++++ 3 files changed, 24 insertions(+) diff --git a/README.md b/README.md index 287c99c761f..571fb130a6c 100644 --- a/README.md +++ b/README.md @@ -35,6 +35,9 @@ [x] 버스트 판정 [x] 총합 구하기 [x] 카드 받기 +[x] 이름 검증 +- 공백 불가 +- 한국어, 알파벳만 허용 ### Dealer diff --git a/src/main/java/common/ErrorMessage.java b/src/main/java/common/ErrorMessage.java index efb4f4d203c..25df3bb9232 100644 --- a/src/main/java/common/ErrorMessage.java +++ b/src/main/java/common/ErrorMessage.java @@ -5,6 +5,7 @@ public enum ErrorMessage { DRAW_CARD_OUT_OF_RANGE("양수 이상의 숫자 중 남은 카드 수 만큼만 선택 가능"), UNSUPPORTED_OPERATION_MESSAGE("해당 객체[%s]에서는 지원하지 않는 메서드입니다 "), NOT_ALLOW_EMPTY_INPUT("공백은 허용되지 않습니다"), + ONLY_KO_AND_ENG("이름은 영어 또는 한국어만 가능합니다: "), MAX_PLAYER_ERROR("최대 인원을 초과했습니다."); private final String message; diff --git a/src/main/java/domain/Participant.java b/src/main/java/domain/Participant.java index 10b99a40dd0..717442a8fcf 100644 --- a/src/main/java/domain/Participant.java +++ b/src/main/java/domain/Participant.java @@ -1,16 +1,36 @@ package domain; import java.util.Optional; +import java.util.regex.Pattern; public abstract class Participant { private final Deck deck; private final String name; protected Participant(Deck participantDeck, String name) { + validateName(name); this.deck = participantDeck; this.name = name; } + private static void validateName(String name) { + Pattern NAME_PATTERN = Pattern.compile("^[a-zA-Z가-힣]+$"); + validateIsNotBlank(name); + validateKoreanAndEnglish(name, NAME_PATTERN); + } + + private static void validateKoreanAndEnglish(String name, Pattern NAME_PATTERN) { + if (!NAME_PATTERN.matcher(name).matches()) { + throw new IllegalArgumentException(ErrorMessage.ONLY_KO_AND_ENG.getMessage()); + } + } + + private static void validateIsNotBlank(String name) { + if (name == null || name.isBlank()) { + throw new IllegalArgumentException(ErrorMessage.NOT_ALLOW_EMPTY_INPUT.getMessage()); + } + } + public Deck getDeck() { return deck; } From 223b55ee66924c5ee462796546d76f077422e361 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sun, 8 Mar 2026 18:55:49 +0900 Subject: [PATCH 043/129] feat: add create result feature --- src/main/java/domain/Result.java | 42 ++++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/main/java/domain/Result.java diff --git a/src/main/java/domain/Result.java b/src/main/java/domain/Result.java new file mode 100644 index 00000000000..7a641ac8987 --- /dev/null +++ b/src/main/java/domain/Result.java @@ -0,0 +1,42 @@ +package domain; + +public enum Result { + 승, 무, 패; + + public static Result determinePlayerResult( + int dealerScore, + int playerScore, + boolean isDealerBust, + boolean isPlayerBust + ) { + if (isDealerBust) { + return Result.승; + } + if (isPlayerBust) { + return Result.패; + } + return compareScoreForCheckPlayerResult(dealerScore, playerScore); + } + + private static Result compareScoreForCheckPlayerResult(int dealerScore, int playerScore) { + if (dealerScore > playerScore) { + return Result.패; + } + if (dealerScore == playerScore) { + return Result.무; + } + return Result.승; + } + + public Result reverse() { + if (this.equals(Result.승)) { + return Result.패; + } + + if (this.equals(Result.패)) { + return Result.승; + } + + return Result.무; + } +} From ce29cb53b29be5f200061df36bdd338a96288b32 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sun, 8 Mar 2026 18:57:24 +0900 Subject: [PATCH 044/129] refactor: divide card creation method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 가독성을 위해 메서드 분리를 진행함 --- .../java/domain/RandomCardCreationStrategy.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/domain/RandomCardCreationStrategy.java b/src/main/java/domain/RandomCardCreationStrategy.java index 3a106a00742..825715cf678 100644 --- a/src/main/java/domain/RandomCardCreationStrategy.java +++ b/src/main/java/domain/RandomCardCreationStrategy.java @@ -14,12 +14,19 @@ public List create() { private List createAllCards() { List cards = new ArrayList<>(); + createAllCardsPerShape(cards); + return cards; + } + + private void createAllCardsPerShape(List cards) { for (CardShape shape : CardShape.values()) { - for (CardContents content : CardContents.values()) { - cards.add(new Card(shape, content)); - } + createSpecificShapeCards(shape, cards); } + } - return cards; + private void createSpecificShapeCards(CardShape shape, List cards) { + for (CardContents content : CardContents.values()) { + cards.add(new Card(shape, content)); + } } } From 91968206d8fb5dd1072c216f9fcd1a394911c9ee Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sun, 8 Mar 2026 19:02:05 +0900 Subject: [PATCH 045/129] refactor: introduce DTO --- src/main/java/domain/Card.java | 4 +++ src/main/java/domain/Dealer.java | 9 ++--- src/main/java/domain/Participant.java | 4 +++ src/main/java/domain/Player.java | 9 ++++- src/main/java/domain/Players.java | 20 +++-------- src/main/java/dto/CardDto.java | 11 ++++++ src/main/java/dto/GameResultDto.java | 41 +++++++++++++++++++++ src/main/java/dto/ParticipantDto.java | 52 +++++++++++++++++++++++++++ 8 files changed, 129 insertions(+), 21 deletions(-) create mode 100644 src/main/java/dto/CardDto.java create mode 100644 src/main/java/dto/GameResultDto.java create mode 100644 src/main/java/dto/ParticipantDto.java diff --git a/src/main/java/domain/Card.java b/src/main/java/domain/Card.java index 2b9e4e5c352..554f812ebab 100644 --- a/src/main/java/domain/Card.java +++ b/src/main/java/domain/Card.java @@ -11,6 +11,10 @@ public Card(CardShape cardShape, CardContents cardContents) { this.cardContents = cardContents; } + public CardShape getCardShape() { + return cardShape; + } + public CardContents getCardContents() { return cardContents; } diff --git a/src/main/java/domain/Dealer.java b/src/main/java/domain/Dealer.java index aeeae217154..a7965c76209 100644 --- a/src/main/java/domain/Dealer.java +++ b/src/main/java/domain/Dealer.java @@ -11,6 +11,11 @@ public Dealer(Deck participantDeck) { super(participantDeck, DEALER_NAME); } + @Override + public List getInitialVisibleCards() { + return List.of(this.getDeck().getCards().getFirst()); + } + @Override public Optional addCard(Deck totalDeck) { if (super.calculateDeckSum() <= MINIMUM_TOTAL_SCORE) { @@ -18,8 +23,4 @@ public Optional addCard(Deck totalDeck) { } return Optional.empty(); } - - public List getCards() { - return getDeck().getCards(); - } } diff --git a/src/main/java/domain/Participant.java b/src/main/java/domain/Participant.java index 717442a8fcf..954d87d4fb7 100644 --- a/src/main/java/domain/Participant.java +++ b/src/main/java/domain/Participant.java @@ -1,5 +1,7 @@ package domain; +import common.ErrorMessage; +import java.util.List; import java.util.Optional; import java.util.regex.Pattern; @@ -39,6 +41,8 @@ public String getName() { return name; } + public abstract List getInitialVisibleCards(); + public boolean isBust() { return deck.isBust(); } diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java index 429d215956b..c528b14da20 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/Player.java @@ -1,9 +1,16 @@ package domain; +import java.util.List; + public class Player extends Participant { public Player(Deck participantDeck, String name) { super(participantDeck, name); } - + + @Override + public List getInitialVisibleCards() { + return super.getDeck().getCards(); + } + } diff --git a/src/main/java/domain/Players.java b/src/main/java/domain/Players.java index 7036c12288e..61627868736 100644 --- a/src/main/java/domain/Players.java +++ b/src/main/java/domain/Players.java @@ -34,6 +34,10 @@ private static List createPlayers(List playerNames, Deck totalDe return players; } + public List getPlayers() { + return players; + } + public Map> getDecksPerPlayer() { Map> decksPerUser = new LinkedHashMap<>(); for (Player player : players) { @@ -44,20 +48,4 @@ public Map> getDecksPerPlayer() { } return decksPerUser; } - -// public List getPlayersNames() { -// -// } - - public boolean isAnyOneGo() { - boolean result = false; - - for (Player player : players) { - if (player.isGo()) { - result = true; - } - } - - return result; - } } diff --git a/src/main/java/dto/CardDto.java b/src/main/java/dto/CardDto.java new file mode 100644 index 00000000000..1936b7d688b --- /dev/null +++ b/src/main/java/dto/CardDto.java @@ -0,0 +1,11 @@ +package dto; + +import domain.Card; + +public record CardDto(String cardShape, String cardContentNumber) { + public static CardDto from(Card card) { + String cardShape = card.getCardShape().name(); + String cardContents = card.getCardContents().getNumber(); + return new CardDto(cardShape, cardContents); + } +} diff --git a/src/main/java/dto/GameResultDto.java b/src/main/java/dto/GameResultDto.java new file mode 100644 index 00000000000..9acc52541e5 --- /dev/null +++ b/src/main/java/dto/GameResultDto.java @@ -0,0 +1,41 @@ +package dto; + +import domain.Dealer; +import domain.Player; +import domain.Players; +import domain.Result; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Map.Entry; + +public record GameResultDto(ParticipantDto dealerDto, + List playerDtos, + Map dealerWinLossResults, + Map playerWinLossResults) { + + public static GameResultDto from(Dealer dealer, Players players, + Map dealerWinLossResults, + Map playerWinLossResults) { + + ParticipantDto dealerDto = ParticipantDto.from(dealer); + + List playerDtos = new ArrayList<>(); + for (Player player : players.getPlayers()) { + playerDtos.add(ParticipantDto.from(player)); + } + + Map dealerResults = new LinkedHashMap<>(); + for (Entry entry : dealerWinLossResults.entrySet()) { + dealerResults.put(entry.getKey().name(), entry.getValue()); + } + + Map playerResults = new LinkedHashMap<>(); + for (Entry entry : playerWinLossResults.entrySet()) { + playerResults.put(entry.getKey().getName(), entry.getValue().name()); + } + + return new GameResultDto(dealerDto, playerDtos, dealerResults, playerResults); + } +} diff --git a/src/main/java/dto/ParticipantDto.java b/src/main/java/dto/ParticipantDto.java new file mode 100644 index 00000000000..fd5b4b2d481 --- /dev/null +++ b/src/main/java/dto/ParticipantDto.java @@ -0,0 +1,52 @@ +package dto; + +import domain.Card; +import domain.Participant; +import java.util.ArrayList; +import java.util.List; + +public record ParticipantDto(String name, List cards, int score) { + public static ParticipantDto initialFrom(Participant participant) { + String name = participant.getName(); + + List cards = new ArrayList<>(); + for (Card card : participant.getInitialVisibleCards()) { + cards.add(CardDto.from(card)); + } + + int score = 0; + return new ParticipantDto(name, cards, score); + } + + public static ParticipantDto from(Participant participant) { + String name = participant.getName(); + + List cards = new ArrayList<>(); + for (Card card : participant.getDeck().getCards()) { + cards.add(CardDto.from(card)); + } + + int score = participant.calculateDeckSum(); + return new ParticipantDto(name, cards, score); + } + + public static List listOf(List participants) { + List participantDtos = new ArrayList<>(); + + for (Participant participant : participants) { + String name = participant.getName(); + + List cards = new ArrayList<>(); + for (Card card : participant.getDeck().getCards()) { + cards.add(CardDto.from(card)); + } + + int score = participant.calculateDeckSum(); + + ParticipantDto participantDto = new ParticipantDto(name, cards, score); + participantDtos.add(participantDto); + } + + return participantDtos; + } +} From aa38ca74993ee279722cb8b829096721fa39cf44 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sun, 8 Mar 2026 19:04:29 +0900 Subject: [PATCH 046/129] feat: bring in Delegate method --- src/main/java/controller/GameDelegate.java | 30 ++++++++++++++++++++++ 1 file changed, 30 insertions(+) create mode 100644 src/main/java/controller/GameDelegate.java diff --git a/src/main/java/controller/GameDelegate.java b/src/main/java/controller/GameDelegate.java new file mode 100644 index 00000000000..d9c94e45ea4 --- /dev/null +++ b/src/main/java/controller/GameDelegate.java @@ -0,0 +1,30 @@ +package controller; + +import dto.GameResultDto; +import dto.ParticipantDto; +import java.util.List; + +public interface GameDelegate { + + /** + * 사용자의 입력을 요구. + */ + List askPlayerNames(); + + boolean askDrawCard(String playerName); + + /** + * 각종 정보 출력 + */ + // 초기 카드를 보여주기 + void showInitialParticipantCards(ParticipantDto dealerDto, List playerDtos); + + // 참가자 한명의 카드를 보여주기 + void showPlayerCards(ParticipantDto participantDto); + + // 딜러가 카드 한 장 더 받았음을 보여주기 + void showDealerOneMoreCardMessage(); + + // 게임의 결과를 보여주기 + void showGameResult(GameResultDto resultDto); +} From 60d6dfb5b00420302007b0e8c873ee5844e88ab5 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sun, 8 Mar 2026 19:15:26 +0900 Subject: [PATCH 047/129] feat: add card info and game result printing feature --- README.md | 54 +++++------ src/main/java/view/OutputView.java | 145 +++++++++++++++++++---------- 2 files changed, 117 insertions(+), 82 deletions(-) diff --git a/README.md b/README.md index 571fb130a6c..fa1eb3755f2 100644 --- a/README.md +++ b/README.md @@ -41,9 +41,8 @@ ### Dealer -[x] 카드를 받는다. - -- 점수 합이 16 이하면 카드를 한장 더 받는다. +- [x] 카드를 받는다. + - 점수 합이 16 이하면 카드를 한장 더 받는다. ### Player @@ -51,50 +50,43 @@ - `hit`를 선택하면 한 장 더 받는다. -[ ] 플레이어의 게임 진행 상태를 결정한다. - -- Hit과 Stand 중 선택헤 자신의 게임 진행 상태를 결정한다. +- [ ] 플레이어의 게임 진행 상태를 결정한다. + - Hit과 Stand 중 선택헤 자신의 게임 진행 상태를 결정한다. ### Card -[x] 카드 점수 계산 - -- 숫자 카드는 숫자 그대로, J/Q/K는 10점 +- [x] 카드 점수 계산 + - 숫자 카드는 숫자 그대로, J/Q/K는 10점 ### Deck -[x] 카드 덱 생성 - -- 전체 카드 덱 + 셔플 -- 참가자 카드 덱 생성 +- [x] 카드 덱 생성 + - 전체 카드 덱 + 셔플 + - 참가자 카드 덱 생성 -[x] 카드 N장 뽑기 -[x] Ace카드를 제외한 카드들의 합계 +- [x] 카드 N장 뽑기 -- 여러 카드의 점수 합산 +- [x] Ace카드를 제외한 카드들의 합계 + - 여러 카드의 점수 합산 -[x] 버스트 판정 - -- 합계가 21 초과인지 판단 - -[x] Ace의 1/11 처리 +- [x] 버스트 판정 + - 합계가 21 초과인지 판단 -- 합계에 따라 1 또는 11로 계산 +- [x] Ace의 1/11 처리 + - 합계에 따라 1 또는 11로 계산 -[x] 카드 넣기 +- [x] 카드 넣기 ### Game -[x] 게임에 필요한 객체들을 생성한다. - -- 전체 덱 객체를 생성한다. -- 딜러 객체를 생성한다. -- 플레이어 객체들을 생성한다. +- [x] 게임에 필요한 객체들을 생성한다. + - 전체 덱 객체를 생성한다. + - 딜러 객체를 생성한다. + - 플레이어 객체들을 생성한다. ## 서비스 ### BlackJackService -[x] 딜러와 플레이어를 생성한다. - -- 딜러와 플레이어에게 각각 전체 덱에서 카드를 뽑아 초기 덱을 지급한다. +- [x] 딜러와 플레이어를 생성한다. + - 딜러와 플레이어에게 각각 전체 덱에서 카드를 뽑아 초기 덱을 지급한다. diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index f0c5576fcd0..05df1b6b18c 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,86 +1,129 @@ package view; -import domain.Card; +import dto.CardDto; +import dto.GameResultDto; +import dto.ParticipantDto; import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Map.Entry; public class OutputView { - private static final String DEALER = "딜러"; + private static final String DELIMITER = ", "; private static final String NAME_PROMPT = "게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)"; private static final String INITIAL_CARD_SHARE = "딜러와 %s에게 2장을 나누었습니다.\n"; private static final String HIT_OR_STAND_PROMPT = "%s는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)\n"; private static final String ADDITIONAL_CARD_FOR_DEALER_DESCRIPTION = "딜러는 16이하라 한장의 카드를 더 받았습니다."; + private static final String PARTICIPANT_CARD_INFO_FORMAT = "%s카드: %s"; + private static final String PARTICIPANT_CARD_INFO_FORMAT_LINE = "%s카드: %s\n"; + private static final String PARTICIPANT_CARD_INFO_WITH_SUM_FORMAT = "%s - 결과: %d\n"; + private static final String WIN_LOSS_RESULT_HEADER = "## 최종 승패\n"; + private static final String WIN_LOSS_RESULT_FORMAT = "%s: %s\n"; - public void printNamePrompt() { - System.out.println(NAME_PROMPT); + public void printErrorMessage(Exception e) { + System.out.println(e.getMessage()); } - public void printInitialCardShare(List playerNames) { - System.out.printf(INITIAL_CARD_SHARE, String.join(", ", playerNames)); + public void printNamePrompt() { + System.out.println(NAME_PROMPT); } - public void printInitialCardShareDetail(Map> results) { + public void printInitialCardShareDetail(ParticipantDto dealerDto, List players) { + List playerNames = new ArrayList<>(); StringBuilder initialCardShareDetail = new StringBuilder(); - List dealerCards = results.remove(DEALER); - initialCardShareDetail.append(consistCardInfo(DEALER, dealerCards)); + String dealerCardInfo = consistCardInfo(PARTICIPANT_CARD_INFO_FORMAT_LINE, dealerDto); + initialCardShareDetail.append(dealerCardInfo); - for (Entry> playersCardInfo : results.entrySet()) { - String playerName = playersCardInfo.getKey(); - List playerCards = playersCardInfo.getValue(); - initialCardShareDetail.append(consistCardInfo(playerName, playerCards)); + for (ParticipantDto playerDto : players) { + playerNames.add(playerDto.name()); + String playerCardInfo = consistCardInfo(PARTICIPANT_CARD_INFO_FORMAT_LINE, playerDto); + initialCardShareDetail.append(playerCardInfo); } + System.out.println(); + System.out.printf(INITIAL_CARD_SHARE, String.join(DELIMITER, playerNames)); System.out.println(initialCardShareDetail); } - private String consistCardInfo(String name, List cards) { - List cardInfos = new ArrayList<>(); - for (Card card : cards) { - cardInfos.add(card.toString()); - } - return String.format("%s: %s\n", name, String.join(", ", cardInfos)); - } - public void printHitOrStandPrompt(String name) { System.out.printf(HIT_OR_STAND_PROMPT, name); } + public void printUserCardInfo(ParticipantDto participantDto) { + String userCardInfo = consistCardInfo(PARTICIPANT_CARD_INFO_FORMAT_LINE, participantDto); + System.out.println(userCardInfo); + } + public void printAdditionalCardForDealerDescription() { + System.out.println(); System.out.println(ADDITIONAL_CARD_FOR_DEALER_DESCRIPTION); } - public void printErrorMessage(Exception e) { - System.out.println(e.getMessage()); + public void printCardInfoWithSum(GameResultDto gameResultDto) { + StringBuilder cardInfoWithSum = new StringBuilder(); + + ParticipantDto dealerDto = gameResultDto.dealerDto(); + String dealerCardInfoWithSum = consistCardInfoWithSum(dealerDto); + cardInfoWithSum.append(dealerCardInfoWithSum); + + for (ParticipantDto playerDto : gameResultDto.playerDtos()) { + String playerCardInfoWithSum = consistCardInfoWithSum(playerDto); + cardInfoWithSum.append(playerCardInfoWithSum); + } + + System.out.println(); + System.out.println(cardInfoWithSum); + } + + public void printWinLossResult(GameResultDto gameResultDto) { + StringBuilder winLossResult = new StringBuilder(); + winLossResult.append(WIN_LOSS_RESULT_HEADER); + + String dealerResult = consistDealerResult(gameResultDto); + winLossResult.append(dealerResult); + + String playerResults = consistPlayerResults(gameResultDto); + winLossResult.append(playerResults); + + System.out.println(winLossResult); + } + + private String consistCardInfo(String infoFormat, ParticipantDto participantDto) { + List cardInfos = new ArrayList<>(); + for (CardDto card : participantDto.cards()) { + cardInfos.add(card.cardContentNumber() + card.cardShape()); + } + return String.format(infoFormat, participantDto.name(), + String.join(DELIMITER, cardInfos)); + } + + private String consistCardInfoWithSum(ParticipantDto participantDto) { + return String.format(PARTICIPANT_CARD_INFO_WITH_SUM_FORMAT, + consistCardInfo(PARTICIPANT_CARD_INFO_FORMAT, participantDto), participantDto.score()); } -} -//게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리) -//pobi,jason -// -// 딜러와 pobi, jason에게 2장을 나누었습니다. -//딜러카드: 3다이아몬드 -//pobi카드: 2하트, 8스페이드 -//jason카드: 7클로버, K스페이드 -// -//pobi는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n) -//y -//pobi카드: 2하트, 8스페이드, A클로버 -//pobi는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n) -//n -//jason는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n) -//n -//jason카드: 7클로버, K스페이드 -// -//딜러는 16이하라 한장의 카드를 더 받았습니다. -// -// 딜러카드: 3다이아몬드, 9클로버, 8다이아몬드 - 결과: 20 -//pobi카드: 2하트, 8스페이드, A클로버 - 결과: 21 -//jason카드: 7클로버, K스페이드 - 결과: 17 -// -// ## 최종 승패 -//딜러: 1승 1패 -//pobi: 승 -//jason: 패 \ No newline at end of file + private String consistDealerResult(GameResultDto gameResultDto) { + String dealerName = gameResultDto.dealerDto().name(); + Map dealerWinLossResults = gameResultDto.dealerWinLossResults(); + StringBuilder result = new StringBuilder(); + for (Entry dealerResult : dealerWinLossResults.entrySet()) { + result.append(dealerResult.getValue()); + result.append(dealerResult.getKey()); + result.append(" "); + } + result.deleteCharAt(result.length() - 1); + return String.format(WIN_LOSS_RESULT_FORMAT, dealerName, result); + } + + private String consistPlayerResults(GameResultDto gameResultDto) { + Map playerWinLossResults = gameResultDto.playerWinLossResults(); + StringBuilder result = new StringBuilder(); + for (Entry playerResult : playerWinLossResults.entrySet()) { + String playerName = playerResult.getKey(); + String playerWinLossResult = playerResult.getValue(); + result.append(String.format(WIN_LOSS_RESULT_FORMAT, playerName, playerWinLossResult)); + } + return result.toString(); + } +} From 2fb6f4b6f05ddacea457943e4b8983903f1b3c0b Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sun, 8 Mar 2026 19:15:40 +0900 Subject: [PATCH 048/129] feat: add card info and game result printing feature --- README.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index fa1eb3755f2..86bbfee5a00 100644 --- a/README.md +++ b/README.md @@ -6,38 +6,39 @@ ### 입력 -[x] 1. 플레이어의 이름을 입력받는다. +- [x] 플레이어의 이름을 입력받는다. + - **(예외 처리)** 공백 문자열인 경우 -- **(예외 처리)** 공백 문자열인 경우 - -[x] 2. hit(`y`) 혹은 stand(`n`)를 입력받는다. - -- **(예외 처리)** `y` 혹은 `n`이 아닌 경우 +- [x] hit(`y`) 혹은 stand(`n`)를 입력받는다. + - **(예외 처리)** `y` 혹은 `n`이 아닌 경우 ### 출력 -[x] 1. 게임 안내 문구를 출력한다. +- [x] 게임 안내 문구를 출력한다. -[x] 2. 초기 카드 내역을 출력한다. +- [x] 초기 카드 내역을 출력한다. -[ ] 3. 최종 카드 내역을 출력한다. +- [x] 최종 카드 내역을 출력한다. -[ ] 4. 최종 승패 결과를 출력한다. +- [x] 최종 승패 결과를 출력한다. ## 도메인 ### Players -[x] 플레이어들을 생성한다. +- [x] 플레이어들을 생성한다. ### Participant -[x] 버스트 판정 -[x] 총합 구하기 -[x] 카드 받기 -[x] 이름 검증 -- 공백 불가 -- 한국어, 알파벳만 허용 +- [x] 버스트 판정 + +- [x] 총합 구하기 + +- [x] 카드 받기 + +- [x] 이름 검증 + - 공백 불가 + - 한국어, 알파벳만 허용 ### Dealer @@ -46,9 +47,8 @@ ### Player -[x] 카드를 받는다. - -- `hit`를 선택하면 한 장 더 받는다. +- [x] 카드를 받는다. + - `hit`를 선택하면 한 장 더 받는다. - [ ] 플레이어의 게임 진행 상태를 결정한다. - Hit과 Stand 중 선택헤 자신의 게임 진행 상태를 결정한다. From 9b63e369f7d58d5cefa1334f3ea71da895f2cffa Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sun, 8 Mar 2026 19:16:42 +0900 Subject: [PATCH 049/129] Revert "feat: add card info and game result printing feature" This reverts commit 2fb6f4b6f05ddacea457943e4b8983903f1b3c0b. --- README.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index 86bbfee5a00..fa1eb3755f2 100644 --- a/README.md +++ b/README.md @@ -6,39 +6,38 @@ ### 입력 -- [x] 플레이어의 이름을 입력받는다. - - **(예외 처리)** 공백 문자열인 경우 +[x] 1. 플레이어의 이름을 입력받는다. -- [x] hit(`y`) 혹은 stand(`n`)를 입력받는다. - - **(예외 처리)** `y` 혹은 `n`이 아닌 경우 +- **(예외 처리)** 공백 문자열인 경우 + +[x] 2. hit(`y`) 혹은 stand(`n`)를 입력받는다. + +- **(예외 처리)** `y` 혹은 `n`이 아닌 경우 ### 출력 -- [x] 게임 안내 문구를 출력한다. +[x] 1. 게임 안내 문구를 출력한다. -- [x] 초기 카드 내역을 출력한다. +[x] 2. 초기 카드 내역을 출력한다. -- [x] 최종 카드 내역을 출력한다. +[ ] 3. 최종 카드 내역을 출력한다. -- [x] 최종 승패 결과를 출력한다. +[ ] 4. 최종 승패 결과를 출력한다. ## 도메인 ### Players -- [x] 플레이어들을 생성한다. +[x] 플레이어들을 생성한다. ### Participant -- [x] 버스트 판정 - -- [x] 총합 구하기 - -- [x] 카드 받기 - -- [x] 이름 검증 - - 공백 불가 - - 한국어, 알파벳만 허용 +[x] 버스트 판정 +[x] 총합 구하기 +[x] 카드 받기 +[x] 이름 검증 +- 공백 불가 +- 한국어, 알파벳만 허용 ### Dealer @@ -47,8 +46,9 @@ ### Player -- [x] 카드를 받는다. - - `hit`를 선택하면 한 장 더 받는다. +[x] 카드를 받는다. + +- `hit`를 선택하면 한 장 더 받는다. - [ ] 플레이어의 게임 진행 상태를 결정한다. - Hit과 Stand 중 선택헤 자신의 게임 진행 상태를 결정한다. From 4283373c5d6a3608410df3fe97e8fb1d578261be Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sun, 8 Mar 2026 19:32:12 +0900 Subject: [PATCH 050/129] Revert "feat: add card info and game result printing feature" This reverts commit 2fb6f4b6f05ddacea457943e4b8983903f1b3c0b. --- README.md | 40 ++++++++++++++++++++-------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/README.md b/README.md index fa1eb3755f2..86bbfee5a00 100644 --- a/README.md +++ b/README.md @@ -6,38 +6,39 @@ ### 입력 -[x] 1. 플레이어의 이름을 입력받는다. +- [x] 플레이어의 이름을 입력받는다. + - **(예외 처리)** 공백 문자열인 경우 -- **(예외 처리)** 공백 문자열인 경우 - -[x] 2. hit(`y`) 혹은 stand(`n`)를 입력받는다. - -- **(예외 처리)** `y` 혹은 `n`이 아닌 경우 +- [x] hit(`y`) 혹은 stand(`n`)를 입력받는다. + - **(예외 처리)** `y` 혹은 `n`이 아닌 경우 ### 출력 -[x] 1. 게임 안내 문구를 출력한다. +- [x] 게임 안내 문구를 출력한다. -[x] 2. 초기 카드 내역을 출력한다. +- [x] 초기 카드 내역을 출력한다. -[ ] 3. 최종 카드 내역을 출력한다. +- [x] 최종 카드 내역을 출력한다. -[ ] 4. 최종 승패 결과를 출력한다. +- [x] 최종 승패 결과를 출력한다. ## 도메인 ### Players -[x] 플레이어들을 생성한다. +- [x] 플레이어들을 생성한다. ### Participant -[x] 버스트 판정 -[x] 총합 구하기 -[x] 카드 받기 -[x] 이름 검증 -- 공백 불가 -- 한국어, 알파벳만 허용 +- [x] 버스트 판정 + +- [x] 총합 구하기 + +- [x] 카드 받기 + +- [x] 이름 검증 + - 공백 불가 + - 한국어, 알파벳만 허용 ### Dealer @@ -46,9 +47,8 @@ ### Player -[x] 카드를 받는다. - -- `hit`를 선택하면 한 장 더 받는다. +- [x] 카드를 받는다. + - `hit`를 선택하면 한 장 더 받는다. - [ ] 플레이어의 게임 진행 상태를 결정한다. - Hit과 Stand 중 선택헤 자신의 게임 진행 상태를 결정한다. From d4c97f6e683964a586c640c1f5d91a10c99868de Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sun, 8 Mar 2026 19:39:22 +0900 Subject: [PATCH 051/129] feat: add flow control logic and game progression feature --- README.md | 16 +- .../java/controller/BlackJackController.java | 85 +++++++++++ src/main/java/domain/Game.java | 84 +++++++++-- src/main/java/service/BlackJackService.java | 19 --- .../controller/BlackJackControllerTest.java | 127 ++++++++++++++++ src/test/java/domain/GameTest.java | 137 +++++++++++++----- 6 files changed, 395 insertions(+), 73 deletions(-) create mode 100644 src/main/java/controller/BlackJackController.java delete mode 100644 src/main/java/service/BlackJackService.java create mode 100644 src/test/java/controller/BlackJackControllerTest.java diff --git a/README.md b/README.md index 86bbfee5a00..ffd790b62e5 100644 --- a/README.md +++ b/README.md @@ -50,9 +50,6 @@ - [x] 카드를 받는다. - `hit`를 선택하면 한 장 더 받는다. -- [ ] 플레이어의 게임 진행 상태를 결정한다. - - Hit과 Stand 중 선택헤 자신의 게임 진행 상태를 결정한다. - ### Card - [x] 카드 점수 계산 @@ -84,9 +81,16 @@ - 딜러 객체를 생성한다. - 플레이어 객체들을 생성한다. -## 서비스 +- [x] 게임을 진행한다 +- [x] 게임 + +## Delegate + +- `Controller` 와 `Game` 의 상호 작용을 위한 도입 -### BlackJackService +### BlackJackController -- [x] 딜러와 플레이어를 생성한다. +- [x] 플레이어 이름을 입력받아 게임에 전달한다 - 딜러와 플레이어에게 각각 전체 덱에서 카드를 뽑아 초기 덱을 지급한다. +- [x] 플레이어의 `hit` or `stand` 여부를 게임이 전달한다. +- [x] 게임에서 결과를 받아 `OutputView` 에 전달한다 \ No newline at end of file diff --git a/src/main/java/controller/BlackJackController.java b/src/main/java/controller/BlackJackController.java new file mode 100644 index 00000000000..da40810ffe1 --- /dev/null +++ b/src/main/java/controller/BlackJackController.java @@ -0,0 +1,85 @@ +package controller; + +import domain.CardCreationStrategy; +import domain.Game; +import dto.GameResultDto; +import dto.ParticipantDto; +import java.util.List; +import java.util.function.BiFunction; +import java.util.function.Consumer; +import view.InputView; +import view.OutputView; + +public class BlackJackController implements GameDelegate { + private final InputView inputView; + private final OutputView outputView; + private final CardCreationStrategy strategy; + + public BlackJackController(InputView inputView, OutputView outputView, CardCreationStrategy strategy) { + this.inputView = inputView; + this.outputView = outputView; + this.strategy = strategy; + } + + public void doGame() { + Game game = retry(Game::ready, this, strategy); + retry(game::play, this); + game.end(this); + } + + @Override + public List askPlayerNames() { + outputView.printNamePrompt(); + return inputView.readNames(); + } + + @Override + public boolean askDrawCard(String playerName) { + outputView.printHitOrStandPrompt(playerName); + String input = inputView.readHitOrStand(); + return input.equals("y"); + } + + @Override + public void showInitialParticipantCards(ParticipantDto dealerDto, List playerDtos) { + outputView.printInitialCardShareDetail(dealerDto, playerDtos); + } + + @Override + public void showPlayerCards(ParticipantDto participantDto) { + outputView.printUserCardInfo(participantDto); + } + + @Override + public void showDealerOneMoreCardMessage() { + outputView.printAdditionalCardForDealerDescription(); + } + + @Override + public void showGameResult(GameResultDto resultDto) { + outputView.printCardInfoWithSum(resultDto); + outputView.printWinLossResult(resultDto); + } + + private R retry(BiFunction biFunction, T input1, U input2) { + while (true) { + try { + return biFunction.apply(input1, input2); + } catch (IllegalArgumentException e) { + outputView.printErrorMessage(e); + } + } + } + + private void retry(Consumer consumer, T input) { + while (true) { + try { + consumer.accept(input); + return; + } catch (IllegalArgumentException e) { + outputView.printErrorMessage(e); + } + } + } +} + diff --git a/src/main/java/domain/Game.java b/src/main/java/domain/Game.java index 12d6a047c7c..c2371730a9c 100644 --- a/src/main/java/domain/Game.java +++ b/src/main/java/domain/Game.java @@ -1,45 +1,103 @@ package domain; +import controller.GameDelegate; +import dto.GameResultDto; +import dto.ParticipantDto; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; public class Game { - private static final String DEALER = "딜러"; - private final Deck totalDeck; private final Dealer dealer; private final Players players; - public Game(Deck totalDeck, Dealer dealer, Players players) { + private Game(Deck totalDeck, Dealer dealer, Players players) { this.totalDeck = totalDeck; this.dealer = dealer; this.players = players; } - public static Game ready(List playerNames, CardCreationStrategy strategy) { + public static Game ready(GameDelegate delegate, CardCreationStrategy strategy) { Deck totalDeck = Deck.createDeck(strategy); Deck dealerDeck = Deck.createParticipantDeck(totalDeck); Dealer dealer = new Dealer(dealerDeck); + List playerNames = delegate.askPlayerNames(); Players players = Players.of(playerNames, totalDeck); - + + delegate.showInitialParticipantCards( + ParticipantDto.initialFrom(dealer), + ParticipantDto.listOf(players.getPlayers()) + ); + return new Game(totalDeck, dealer, players); } - public Map> showInitialCardShareResult() { - Map> result = players.getDecksPerPlayer(); + public void play(GameDelegate observer) { + List individualPlayers = players.getPlayers(); + for (Player player : individualPlayers) { + while (!player.isBust() && observer.askDrawCard(player.getName())) { + player.addCard(totalDeck); + observer.showPlayerCards(ParticipantDto.from(player)); + } + } + while (dealer.addCard(totalDeck).isPresent()) { + observer.showDealerOneMoreCardMessage(); + } + } + + public void end(GameDelegate delegate) { + GameResultDto result = this.calculateResult(); + delegate.showGameResult(result); + } - List dealerCard = dealer.getCards(); - dealerCard.removeLast(); + private GameResultDto calculateResult() { + int dealerScore = dealer.calculateDeckSum(); + boolean isDealerBust = dealer.isBust(); - result.put(DEALER, dealerCard); + Map playerWinLossResults = consistPlayerWinLossResults(dealerScore, isDealerBust); + Map dealerWinLossResults = consistDealerResult(playerWinLossResults); - return result; + return GameResultDto.from( + dealer, + players, + dealerWinLossResults, + playerWinLossResults + ); } - public void play() { - //한 턴만. -> isAnyOneGo 안씀 + private Map consistPlayerWinLossResults(int dealerScore, boolean isDealerBust) { + Map playerWinLossResults = new LinkedHashMap<>(); + List playingPlayers = players.getPlayers(); + + for (Player specificPlayer : playingPlayers) { + Result specificPlayerResult = determinePlayerResult(dealerScore, isDealerBust, specificPlayer); + playerWinLossResults.put(specificPlayer, specificPlayerResult); + } + + return playerWinLossResults; + } + + private Result determinePlayerResult(int dealerScore, boolean isDealerBust, Player specificPlayer) { + return Result.determinePlayerResult( + dealerScore, + specificPlayer.calculateDeckSum(), + isDealerBust, + specificPlayer.isBust() + ); + } + private Map consistDealerResult(Map playerWinLossResults) { + Map dealerWinLossResults = new HashMap<>(); + List playingPlayers = playerWinLossResults.keySet().stream().toList(); + for (Player player : playingPlayers) { + Result dealerResult = playerWinLossResults.get(player).reverse(); + int currentValue = dealerWinLossResults.getOrDefault(dealerResult, 0); + dealerWinLossResults.put(dealerResult, currentValue + 1); + } + return dealerWinLossResults; } } diff --git a/src/main/java/service/BlackJackService.java b/src/main/java/service/BlackJackService.java deleted file mode 100644 index d0072f332f8..00000000000 --- a/src/main/java/service/BlackJackService.java +++ /dev/null @@ -1,19 +0,0 @@ -package service; - -import domain.CardCreationStrategy; -import domain.Game; -import java.util.List; - -public class BlackJackService { - private final CardCreationStrategy strategy; - - public BlackJackService(CardCreationStrategy strategy) { - this.strategy = strategy; - } - - public Game prepareGame(List playerNames) { - return Game.ready(playerNames, strategy); - } - - -} diff --git a/src/test/java/controller/BlackJackControllerTest.java b/src/test/java/controller/BlackJackControllerTest.java new file mode 100644 index 00000000000..cdb58586bdc --- /dev/null +++ b/src/test/java/controller/BlackJackControllerTest.java @@ -0,0 +1,127 @@ +package controller; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; + +import domain.Card; +import domain.CardContents; +import domain.CardCreationStrategy; +import domain.CardShape; +import domain.Deck; +import domain.Player; +import dto.ParticipantDto; +import java.util.ArrayList; +import java.util.List; +import org.assertj.core.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +import view.InputView; +import view.OutputView; + +@ExtendWith(MockitoExtension.class) +class BlackJackControllerTest { + private static final String TEST_NAME = "tester"; + + @Mock + private InputView inputView; + + @Mock + private OutputView outputView; + + @Mock + private CardCreationStrategy strategy; + + @InjectMocks + private BlackJackController controller; + + @Test + @DisplayName("이름 잘 물어봄") + void askPlayerNames_success() { + //given + when(inputView.readNames()).thenReturn(List.of("pobi", "gump")); + + //when + List result = controller.askPlayerNames(); + + //then + verify(outputView, times(1)).printNamePrompt(); + verify(inputView, times(1)).readNames(); + } + + @Test + @DisplayName("카드 내용 출력 전달 잘함") + void showPlayerCards_success() { + //given + List testCards = List.of( + new Card(CardShape.하트, CardContents.TWO), + new Card(CardShape.하트, CardContents.THREE), + new Card(CardShape.하트, CardContents.FOUR) + ); + CardCreationStrategy strategy = new CardCreationStrategy() { + @Override + public List create() { + return new ArrayList<>(testCards); + } + }; + + Deck totalDeck = Deck.createDeck(strategy); + Deck playerDeck = Deck.createParticipantDeck(totalDeck); + Player PLAYER = new Player(playerDeck, TEST_NAME); + ParticipantDto PARTICIPANT_DTO = ParticipantDto.from(PLAYER); + + //when && then + assertDoesNotThrow( + () -> controller.showPlayerCards(PARTICIPANT_DTO) + ); + } + + @Nested + class addDrawCardTest { + @Test + @DisplayName("질문해서 y 면 true 반환") + void askDrawCard_true() { + //given + when(inputView.readHitOrStand()).thenReturn("y"); + + //when + boolean result = controller.askDrawCard(TEST_NAME); + + //then + Assertions.assertThat(result).isTrue(); + } + + @Test + @DisplayName("질문해서 n이면 false 반환") + void askDrawCard_false() { + //given + when(inputView.readHitOrStand()).thenReturn("n"); + + //when + boolean result = controller.askDrawCard(TEST_NAME); + + //then + Assertions.assertThat(result).isFalse(); + } + } + + //private List createSampleCards() { +// CardShape[] shapes = CardShape.values(); +// CardContents[] contents = CardContents.values(); +// +// List sampleCards = new ArrayList<>(); +// for (CardShape cardShape : shapes) { +// for (CardContents content : contents) { +// sampleCards.add(new Card(cardShape, content)); +// } +// } +// +// return sampleCards; +// } +} diff --git a/src/test/java/domain/GameTest.java b/src/test/java/domain/GameTest.java index 1e8ec8b97f0..563be961163 100644 --- a/src/test/java/domain/GameTest.java +++ b/src/test/java/domain/GameTest.java @@ -1,66 +1,133 @@ package domain; -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.assertj.core.api.Assertions.assertThat; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.mockito.Mockito.atLeast; +import static org.mockito.Mockito.times; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import controller.GameDelegate; +import dto.GameResultDto; import java.util.ArrayList; +import java.util.HashMap; +import java.util.LinkedHashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.ArgumentCaptor; +import org.mockito.Captor; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; +@ExtendWith(MockitoExtension.class) class GameTest { + private static final List TEST_PLAYER_NAMES = List.of("pobi", "terry", "rati", "gump"); + private static final CardCreationStrategy STRATEGY = GameTest::createSampleCards; + @Mock + GameDelegate gameDelegate; + @Captor + ArgumentCaptor gameResultDtoArgumentCaptor; + + private static List createSampleCards() { + CardShape[] shapes = CardShape.values(); + CardContents[] contents = CardContents.values(); + + List sampleCards = new ArrayList<>(); + for (CardShape cardShape : shapes) { + for (CardContents content : contents) { + sampleCards.add(new Card(cardShape, content)); + } + } + + return sampleCards; + } + @Test @DisplayName("생성 잘 한다") void ready_good() { //given - List testPlayerNames = List.of("pobi", "terry", "rati", "gump"); - CardCreationStrategy totalCardCreationStrategy = this::createSampleCards; //when, then assertDoesNotThrow( - () -> Game.ready(testPlayerNames, totalCardCreationStrategy) + () -> Game.ready(gameDelegate, STRATEGY) ); } @Test - @DisplayName("초기 카드 배분 시 딜러는 1장, 플레이어는 2장씩 출력되어야 한다") - void showInitialCardShareResult_success() { - // given - List testPlayerNames = List.of("pobi", "terry", "rati", "gump"); - Game game = Game.ready(testPlayerNames, this::createSampleCards); - - // when - Map> result = game.showInitialCardShareResult(); + @DisplayName("게임 플레이 잘함") + void play_success() { + //given + when(gameDelegate.askPlayerNames()).thenReturn(TEST_PLAYER_NAMES); + Game game = Game.ready(gameDelegate, STRATEGY); - // then - assertThat(result.get("딜러").size()).isEqualTo(1); + //when + game.play(gameDelegate); - testPlayerNames.forEach(name -> - assertThat(result.get(name).size()).isEqualTo(2) - ); + //then + for (String name : TEST_PLAYER_NAMES) { + verify(gameDelegate, atLeast(1)).askDrawCard(name); + } + verify(gameDelegate, atLeast(1)).showDealerOneMoreCardMessage(); - System.out.println("========= 초기 배분 결과 ========="); - result.forEach((name, cards) -> { - String cardInfo = cards.stream() - .map(Card::toString) - .collect(Collectors.joining(", ")); - System.out.printf("%s : [ %s ]\n", name, cardInfo); - }); } - private List createSampleCards() { - CardShape[] shapes = CardShape.values(); - CardContents[] contents = CardContents.values(); - - List sampleCards = new ArrayList<>(); - for (CardShape cardShape : shapes) { - for (CardContents content : contents) { - sampleCards.add(new Card(cardShape, content)); + @Test + @DisplayName("결과 계산 잘함") + void calculate_success() { + //given + CardCreationStrategy strategy = new CardCreationStrategy() { + @Override + public List create() { + return new ArrayList<>( + List.of( + new Card(CardShape.하트, CardContents.J), + new Card(CardShape.하트, CardContents.EIGHT), + new Card(CardShape.다이아몬드, CardContents.J), + new Card(CardShape.다이아몬드, CardContents.THREE), + new Card(CardShape.스페이드, CardContents.J), + new Card(CardShape.스페이드, CardContents.THREE), + new Card(CardShape.클로버, CardContents.J), + new Card(CardShape.클로버, CardContents.NINE), + new Card(CardShape.하트, CardContents.K), + new Card(CardShape.하트, CardContents.NINE) + ) + ); } - } + }; - return sampleCards; + when(gameDelegate.askPlayerNames()).thenReturn(TEST_PLAYER_NAMES); + Game game = Game.ready(gameDelegate, strategy); + Map expectDealerWinLossResults = consistExpectDealerWinLossResults(); + Map expectPlayerWinLossResults = consistExpectPlayerWinLossResults(TEST_PLAYER_NAMES); + + //when + game.play(gameDelegate); + game.end(gameDelegate); + + //then + verify(gameDelegate, times(1)) + .showGameResult(gameResultDtoArgumentCaptor.capture()); + GameResultDto result = gameResultDtoArgumentCaptor.getValue(); + assertThat(result.dealerWinLossResults()).isEqualTo(expectDealerWinLossResults); + assertThat(result.playerWinLossResults()).isEqualTo(expectPlayerWinLossResults); + } + + private Map consistExpectPlayerWinLossResults(List testPlayerNames) { + Map expectPlayerWinLossResults = new LinkedHashMap<>(); + expectPlayerWinLossResults.put("pobi", "패"); + expectPlayerWinLossResults.put("terry", "패"); + expectPlayerWinLossResults.put("rati", "승"); + expectPlayerWinLossResults.put("gump", "승"); + return expectPlayerWinLossResults; + } + + private Map consistExpectDealerWinLossResults() { + Map expectDealerWinLossResults = new HashMap<>(); + expectDealerWinLossResults.put("승", 2); + expectDealerWinLossResults.put("패", 2); + return expectDealerWinLossResults; } } \ No newline at end of file From c8818cd6a1bf6d26675e1bd5fb8f222224182d02 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Sun, 8 Mar 2026 19:40:25 +0900 Subject: [PATCH 052/129] feat: assemble components for running --- src/main/java/Main.java | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/main/java/Main.java b/src/main/java/Main.java index 6054a646969..8b594a94ee4 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -1,5 +1,15 @@ +import controller.BlackJackController; +import domain.CardCreationStrategy; +import domain.RandomCardCreationStrategy; +import view.InputView; +import view.OutputView; + public class Main { public static void main(String[] args) { + InputView inputView = new InputView(); + OutputView outputView = new OutputView(); + CardCreationStrategy strategy = new RandomCardCreationStrategy(); + new BlackJackController(inputView, outputView, strategy).doGame(); } } From 8bf85fb302f94dc61a6d1a44d03b946ec7468aae Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Tue, 10 Mar 2026 19:22:36 +0900 Subject: [PATCH 053/129] fix : change position of NAME_PATTERN MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 한 번 사용되는 NAME_PATTERN을 매개변수가 아닌 실제 사용되는 메서드 내부로 이동하여서 메서드 내부의 응집도를 높였습니다. --- src/main/java/domain/Participant.java | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/main/java/domain/Participant.java b/src/main/java/domain/Participant.java index 954d87d4fb7..b31ed89945b 100644 --- a/src/main/java/domain/Participant.java +++ b/src/main/java/domain/Participant.java @@ -16,18 +16,19 @@ protected Participant(Deck participantDeck, String name) { } private static void validateName(String name) { - Pattern NAME_PATTERN = Pattern.compile("^[a-zA-Z가-힣]+$"); validateIsNotBlank(name); - validateKoreanAndEnglish(name, NAME_PATTERN); + validateKoreanAndEnglish(name); } - private static void validateKoreanAndEnglish(String name, Pattern NAME_PATTERN) { + private static void validateKoreanAndEnglish(String name) { + Pattern NAME_PATTERN = Pattern.compile("^[a-zA-Z가-힣]+$"); if (!NAME_PATTERN.matcher(name).matches()) { throw new IllegalArgumentException(ErrorMessage.ONLY_KO_AND_ENG.getMessage()); } } private static void validateIsNotBlank(String name) { + if (name == null || name.isBlank()) { throw new IllegalArgumentException(ErrorMessage.NOT_ALLOW_EMPTY_INPUT.getMessage()); } From b7335e9fee91b76ea5a8ff2c36ed668903a85c70 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Tue, 10 Mar 2026 19:50:54 +0900 Subject: [PATCH 054/129] fix : change method type to static MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 객체 생성 비용을 고려하여 static 메서드로 변경하였습니다 --- src/main/java/domain/AceScoreDiscriminator.java | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/main/java/domain/AceScoreDiscriminator.java b/src/main/java/domain/AceScoreDiscriminator.java index d4acdd22aab..0ec6a2012a4 100644 --- a/src/main/java/domain/AceScoreDiscriminator.java +++ b/src/main/java/domain/AceScoreDiscriminator.java @@ -5,7 +5,7 @@ public class AceScoreDiscriminator { private static final int BUST_CRITERIA = 21; - public int calculateAceCardsSum(List cards, int sumExceptAce) { + public static int calculateAceCardsSum(List cards, int sumExceptAce) { int aceCount = countAce(cards); int aceSum = 0; @@ -20,14 +20,13 @@ public int calculateAceCardsSum(List cards, int sumExceptAce) { return aceSum; } - private int countAce(List cards) { + private static int countAce(List cards) { int count = 0; for (Card card : cards) { if (card.isAce()) { count++; } } - return count; } } From 2e065d3fb9f58f4af2e621d3d2f3462d37cfc9a9 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Tue, 10 Mar 2026 21:09:39 +0900 Subject: [PATCH 055/129] refactor : delete .gitkeep --- src/test/java/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/test/java/.gitkeep diff --git a/src/test/java/.gitkeep b/src/test/java/.gitkeep deleted file mode 100644 index e69de29bb2d..00000000000 From 49b26f405c29fe7b240fa8dafc936d8c31900acc Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Tue, 10 Mar 2026 21:10:49 +0900 Subject: [PATCH 056/129] fix : change the method call method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 메서드가 static 으로 변경됨에 따라 테스트에서도 static 메서드 호출 방식으로 변경함 --- src/test/java/domain/AceScoreDiscriminatorTest.java | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/src/test/java/domain/AceScoreDiscriminatorTest.java b/src/test/java/domain/AceScoreDiscriminatorTest.java index 40157e01ed8..a150607d7ca 100644 --- a/src/test/java/domain/AceScoreDiscriminatorTest.java +++ b/src/test/java/domain/AceScoreDiscriminatorTest.java @@ -7,8 +7,6 @@ public class AceScoreDiscriminatorTest { - private final AceScoreDiscriminator discriminator = new AceScoreDiscriminator(); - @Test @DisplayName("Ace 1개 있을 때 값 판별 잘함") void calculateAceCardSum_success() { @@ -21,7 +19,7 @@ void calculateAceCardSum_success() { int expectedAceCardsSum = 11; //when - int result = discriminator.calculateAceCardsSum(testCards, testSumExceptAce); + int result = AceScoreDiscriminator.calculateAceCardsSum(testCards, testSumExceptAce); //then Assertions.assertEquals(expectedAceCardsSum, result); @@ -40,7 +38,7 @@ void calculateAceCardsSum_success() { int expectedAceCardsSum = 2; //when - int result = discriminator.calculateAceCardsSum(testCards, testSumExceptAce); + int result = AceScoreDiscriminator.calculateAceCardsSum(testCards, testSumExceptAce); //then Assertions.assertEquals(expectedAceCardsSum, result); From 68b5af8c187b49feb4f3431f20617b01b2c27cdb Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Tue, 10 Mar 2026 21:12:25 +0900 Subject: [PATCH 057/129] fix : change variable type list to deque MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit deck의 역할이 전체 카드를 보관 및 제공하는 것으로 축소됨에 따라 더욱 적합하다고 생각되는 타입으로 변경 예정. 그에 따른 카드 생성 전략의 반환 타입 변경 --- src/main/java/domain/RandomCardCreationStrategy.java | 6 ++++-- src/test/java/domain/RandomCardCreationStrategyTest.java | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/main/java/domain/RandomCardCreationStrategy.java b/src/main/java/domain/RandomCardCreationStrategy.java index 825715cf678..1ea9d7bfa5e 100644 --- a/src/main/java/domain/RandomCardCreationStrategy.java +++ b/src/main/java/domain/RandomCardCreationStrategy.java @@ -1,15 +1,17 @@ package domain; +import java.util.ArrayDeque; import java.util.ArrayList; import java.util.Collections; +import java.util.Deque; import java.util.List; public class RandomCardCreationStrategy implements CardCreationStrategy { @Override - public List create() { + public Deque create() { List cards = createAllCards(); Collections.shuffle(cards); - return cards; + return new ArrayDeque<>(cards); } private List createAllCards() { diff --git a/src/test/java/domain/RandomCardCreationStrategyTest.java b/src/test/java/domain/RandomCardCreationStrategyTest.java index 8dca2a4f5b9..92af2214c24 100644 --- a/src/test/java/domain/RandomCardCreationStrategyTest.java +++ b/src/test/java/domain/RandomCardCreationStrategyTest.java @@ -1,6 +1,6 @@ package domain; -import java.util.List; +import java.util.Deque; import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -15,7 +15,7 @@ void creation_success() { CardCreationStrategy strategy = new RandomCardCreationStrategy(); //when - List cards = strategy.create(); + Deque cards = strategy.create(); //then Assertions.assertThat(cards.size()).isEqualTo(expectCardSize); From 77c2d39dc03e4cf7c6a4d3c5957aabd109c13b66 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Tue, 10 Mar 2026 22:50:46 +0900 Subject: [PATCH 058/129] fix : separate Deck MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Deck 의 책임이 많은 것 같아서 분리를 진행 - 전체 덱 - 참가자 카드 덱(HAND) --- src/main/java/domain/Deck.java | 62 +++------ src/main/java/domain/Hand.java | 46 +++++++ src/test/java/domain/DeckTest.java | 207 +++++++++++------------------ 3 files changed, 140 insertions(+), 175 deletions(-) create mode 100644 src/main/java/domain/Hand.java diff --git a/src/main/java/domain/Deck.java b/src/main/java/domain/Deck.java index 71e472624a1..c409fed5313 100644 --- a/src/main/java/domain/Deck.java +++ b/src/main/java/domain/Deck.java @@ -1,73 +1,41 @@ package domain; import common.ErrorMessage; -import java.util.ArrayList; +import java.util.Deque; import java.util.List; +import java.util.NoSuchElementException; public class Deck { - private static final int BUST_CRITERIA = 21; - private final List cards; + private final Deque cards; - private Deck(List cards) { + private Deck(Deque cards) { this.cards = cards; } public static Deck createDeck(CardCreationStrategy strategy) { - List cards = strategy.create(); - return new Deck(cards); + return new Deck(strategy.create()); } - public static Deck createParticipantDeck(Deck totaldeck) { - List cards = new ArrayList<>( - List.of( - totaldeck.drawCard(), - totaldeck.drawCard() - ) - ); - return new Deck(cards); - } - - public List getCards() { - return cards; - } + // 메시지 : 카드를 만들어줘. public Card drawCard() { - if (cards.isEmpty()) { + try { + return cards.pop(); + } catch (NoSuchElementException e) { throw new IllegalArgumentException(ErrorMessage.DRAW_CARD_OUT_OF_RANGE.getMessage()); } - - return cards.removeFirst(); } - public boolean isBust() { - return calculateCardScoreSum() > BUST_CRITERIA; - } - - public int calculateCardScoreSum() { - int sumExceptAce = calculateCardScoreSumExceptAce(); - int sumAce = new AceScoreDiscriminator().calculateAceCardsSum(cards, sumExceptAce); - - return sumAce + sumExceptAce; + public List drawTwoCards() { + try { + return List.of(cards.pop(), cards.pop()); + } catch (NoSuchElementException e) { + throw new IllegalArgumentException(ErrorMessage.DRAW_CARD_OUT_OF_RANGE.getMessage()); + } } public int addCard(Card card) { this.cards.add(card); return cards.size(); } - - private int calculateCardScoreSumExceptAce() { - int sum = 0; - for (Card card : cards) { - sum = addCardScoreExceptAce(card, sum); - } - - return sum; - } - - private int addCardScoreExceptAce(Card card, int sum) { - if (!card.isAce()) { - sum += card.getCardContents().getScore(); - } - return sum; - } } diff --git a/src/main/java/domain/Hand.java b/src/main/java/domain/Hand.java new file mode 100644 index 00000000000..46baac4cdcf --- /dev/null +++ b/src/main/java/domain/Hand.java @@ -0,0 +1,46 @@ +package domain; + +import java.util.ArrayList; +import java.util.List; + +public class Hand { + private static final int BUST_CRITERIA = 21; + private final List cards; + + private Hand(List cards) { + this.cards = new ArrayList<>(cards); + } + + public static Hand of(Card card1, Card card2) { + return new Hand(List.of(card1, card2)); + } + + //사용자 카드 보관함을 생성해줘!! + + public void addCard(Card card) { + this.cards.add(card); + } + + public int calculateCardScoreSum() { + int sumExceptAce = calculateCardScoreSumExceptAce(); + int sumAce = AceScoreDiscriminator.calculateAceCardsSum(cards, sumExceptAce); + + return sumAce + sumExceptAce; + } + + private int calculateCardScoreSumExceptAce() { + int sum = 0; + for (Card card : cards) { + sum = addCardScoreExceptAce(card, sum); + } + + return sum; + } + + private int addCardScoreExceptAce(Card card, int sum) { + if (!card.isAce()) { + sum += card.getCardContents().getScore(); + } + return sum; + } +} \ No newline at end of file diff --git a/src/test/java/domain/DeckTest.java b/src/test/java/domain/DeckTest.java index c44c942bdf0..c18d6934988 100644 --- a/src/test/java/domain/DeckTest.java +++ b/src/test/java/domain/DeckTest.java @@ -1,28 +1,23 @@ package domain; -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertFalse; -import static org.junit.jupiter.api.Assertions.assertTrue; -import common.ErrorMessage; -import java.util.ArrayList; +import java.util.ArrayDeque; +import java.util.Deque; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; class DeckTest { Deck deck; CardCreationStrategy fixedCardCreationStrategy = new CardCreationStrategy() { @Override - public List create() { + public Deque create() { Card spadeJ = new Card(CardShape.스페이드, CardContents.J); Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); - return new ArrayList<>(List.of(spadeJ, clover5)); + return new ArrayDeque<>(List.of(spadeJ, clover5)); } }; @@ -38,123 +33,79 @@ void deck_create_success() { () -> Deck.createDeck(fixedCardCreationStrategy) ); } - - @Test - @DisplayName("참가자의 Deck를 생성할 때 오류 발생 안함") - void initial_deck_create_success() { - assertDoesNotThrow( - () -> Deck.createParticipantDeck(deck) - ); - } - - @Test - @DisplayName("카드들의 점수 합을 구함") - void calculate_card_score_sum() { - CardCreationStrategy fixedCardCreationStrategy = new CardCreationStrategy() { - @Override - public List create() { - Card spadeJ = new Card(CardShape.스페이드, CardContents.J); - Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); - Card diamondAce = new Card(CardShape.다이아몬드, CardContents.A); - - return List.of(spadeJ, clover5, diamondAce); - } - }; - Deck deck = Deck.createDeck(fixedCardCreationStrategy); - assertThat(deck.calculateCardScoreSum()).isEqualTo(16); - } - - @Test - @DisplayName("덱에 카드 한장을 추가함") - void add_card() { - // given - CardCreationStrategy gameStrategy = new CardCreationStrategy() { - @Override - public List create() { - Card spadeJ = new Card(CardShape.스페이드, CardContents.J); - Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); - Card diamond3 = new Card(CardShape.다이아몬드, CardContents.THREE); - - return new ArrayList<>(List.of(spadeJ, clover5, diamond3)); - } - }; - Deck gameDeck = Deck.createDeck(gameStrategy); - - CardCreationStrategy playerStrategy = new CardCreationStrategy() { - @Override - public List create() { - Card spadeA = new Card(CardShape.스페이드, CardContents.A); - - return new ArrayList<>(List.of(spadeA)); - } - }; - Deck playerDeck = Deck.createDeck(playerStrategy); - - // when - int result = playerDeck.addCard(gameDeck.drawCard()); - int expected = 2; - - // then - assertThat(result).isEqualTo(expected); - } - - @Nested - class drawTest { - @Test - @DisplayName("Deck에서 카드를 한장 뽑아줌") - void draw_card_success() { - Card result = deck.drawCard(); - Card expected = new Card(CardShape.스페이드, CardContents.J); - assertThat(result).isEqualTo(expected); - } - - @Test - @DisplayName("Deck에서 0이하 혹은 남은 카드 이상의 숫자 선택 시도 시 오류 발생") - void draw_card_fail() { - deck.drawCard(); - deck.drawCard(); - - assertThatThrownBy( - () -> deck.drawCard() - ).isInstanceOf(IllegalArgumentException.class) - .hasMessage(ErrorMessage.DRAW_CARD_OUT_OF_RANGE.getMessage()); - } - } - - @Nested - class isBustTest { - @Test - @DisplayName("카드의 합이 21이 넘어가면 버스트로 판정") - void isBust_true() { - CardCreationStrategy fixedCardCreationStrategy = new CardCreationStrategy() { - @Override - public List create() { - Card spadeJ = new Card(CardShape.스페이드, CardContents.J); - Card cloverQ = new Card(CardShape.클로버, CardContents.Q); - Card diamondK = new Card(CardShape.다이아몬드, CardContents.K); - - return List.of(spadeJ, cloverQ, diamondK); - } - }; - Deck deck = Deck.createDeck(fixedCardCreationStrategy); - assertTrue(deck.isBust()); - } - - @Test - @DisplayName("카드의 합이 21이 넘어가면 버스트로 판정하지 않음") - void isBust_false() { - CardCreationStrategy fixedCardCreationStrategy = new CardCreationStrategy() { - @Override - public List create() { - Card spadeJ = new Card(CardShape.스페이드, CardContents.J); - Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); - Card diamond3 = new Card(CardShape.다이아몬드, CardContents.THREE); - - return List.of(spadeJ, clover5, diamond3); - } - }; - Deck deck = Deck.createDeck(fixedCardCreationStrategy); - assertFalse(deck.isBust()); - } - } +// +// @Test +// @DisplayName("카드들의 점수 합을 구함") +// void calculate_card_score_sum() { +// CardCreationStrategy fixedCardCreationStrategy = new CardCreationStrategy() { +// @Override +// public Deque create() { +// Card spadeJ = new Card(CardShape.스페이드, CardContents.J); +// Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); +// Card diamondAce = new Card(CardShape.다이아몬드, CardContents.A); +// +// return List.of(spadeJ, clover5, diamondAce); +// } +// }; +// Deck deck = Deck.createDeck(fixedCardCreationStrategy); +// Deck deck = Deck.createDeck(fixedCardCreationStrategy); +// assertThat(deck.calculateCardScoreSum()).isEqualTo(16); +// } +// +// @Test +// @DisplayName("덱에 카드 한장을 추가함") +// void add_card() { +// // given +// CardCreationStrategy gameStrategy = new CardCreationStrategy() { +// @Override +// public List create() { +// Card spadeJ = new Card(CardShape.스페이드, CardContents.J); +// Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); +// Card diamond3 = new Card(CardShape.다이아몬드, CardContents.THREE); +// +// return new ArrayList<>(List.of(spadeJ, clover5, diamond3)); +// } +// }; +// Deck gameDeck = Deck.createDeck(gameStrategy); +// +// CardCreationStrategy playerStrategy = new CardCreationStrategy() { +// @Override +// public List create() { +// Card spadeA = new Card(CardShape.스페이드, CardContents.A); +// +// return new ArrayList<>(List.of(spadeA)); +// } +// }; +// Deck playerDeck = Deck.createDeck(playerStrategy); +// +// // when +// int result = playerDeck.addCard(gameDeck.drawCard()); +// int expected = 2; +// +// // then +// assertThat(result).isEqualTo(expected); +// } +// +// @Nested +// class drawTest { +// @Test +// @DisplayName("Deck에서 카드를 한장 뽑아줌") +// void draw_card_success() { +// Card result = deck.drawCard(); +// Card expected = new Card(CardShape.스페이드, CardContents.J); +// assertThat(result).isEqualTo(expected); +// } +// +// @Test +// @DisplayName("Deck에서 0이하 혹은 남은 카드 이상의 숫자 선택 시도 시 오류 발생") +// void draw_card_fail() { +// deck.drawCard(); +// deck.drawCard(); +// +// assertThatThrownBy( +// () -> deck.drawCard() +// ).isInstanceOf(IllegalArgumentException.class) +// .hasMessage(ErrorMessage.DRAW_CARD_OUT_OF_RANGE.getMessage()); +// } +// } } \ No newline at end of file From 368930bd1ad1bda5c4e09168013e9f2379cf24d5 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Tue, 10 Mar 2026 22:52:57 +0900 Subject: [PATCH 059/129] fix : change List to Deque --- src/main/java/domain/CardCreationStrategy.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/domain/CardCreationStrategy.java b/src/main/java/domain/CardCreationStrategy.java index 85b31b6c773..9e837e90fad 100644 --- a/src/main/java/domain/CardCreationStrategy.java +++ b/src/main/java/domain/CardCreationStrategy.java @@ -1,7 +1,7 @@ package domain; -import java.util.List; +import java.util.Deque; public interface CardCreationStrategy { - List create(); + Deque create(); } From 468c59c51486e9c1915d6db790ce75f35728bfd8 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Tue, 10 Mar 2026 22:53:43 +0900 Subject: [PATCH 060/129] fix : change Deck to Hand MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이전의 변경에 따라 참가자들의 Deck를 Hand로 변경 --- src/main/java/domain/Dealer.java | 26 +++++--------- src/main/java/domain/Participant.java | 51 ++++++++++++--------------- src/main/java/domain/Player.java | 13 ++----- 3 files changed, 33 insertions(+), 57 deletions(-) diff --git a/src/main/java/domain/Dealer.java b/src/main/java/domain/Dealer.java index a7965c76209..5d19c295eeb 100644 --- a/src/main/java/domain/Dealer.java +++ b/src/main/java/domain/Dealer.java @@ -1,26 +1,18 @@ package domain; -import java.util.List; -import java.util.Optional; - public class Dealer extends Participant { private static final int MINIMUM_TOTAL_SCORE = 16; private static final String DEALER_NAME = "딜러"; - public Dealer(Deck participantDeck) { - super(participantDeck, DEALER_NAME); - } - - @Override - public List getInitialVisibleCards() { - return List.of(this.getDeck().getCards().getFirst()); + public Dealer(Card card1, Card card2) { + super(DEALER_NAME, card1, card2); } - @Override - public Optional addCard(Deck totalDeck) { - if (super.calculateDeckSum() <= MINIMUM_TOTAL_SCORE) { - return super.addCard(totalDeck); - } - return Optional.empty(); - } +// @Override +// public Optional addCard(Deck totalDeck) { +// if (super.calculateHandSum() <= MINIMUM_TOTAL_SCORE) { +// return super.addCard(totalDeck); +// } +// return Optional.empty(); +// } } diff --git a/src/main/java/domain/Participant.java b/src/main/java/domain/Participant.java index b31ed89945b..1ae37951372 100644 --- a/src/main/java/domain/Participant.java +++ b/src/main/java/domain/Participant.java @@ -1,17 +1,15 @@ package domain; import common.ErrorMessage; -import java.util.List; -import java.util.Optional; import java.util.regex.Pattern; public abstract class Participant { - private final Deck deck; + private final Hand hand; private final String name; - protected Participant(Deck participantDeck, String name) { + protected Participant(String name, Card card1, Card card2) { + this.hand = Hand.of(card1, card2); validateName(name); - this.deck = participantDeck; this.name = name; } @@ -28,33 +26,28 @@ private static void validateKoreanAndEnglish(String name) { } private static void validateIsNotBlank(String name) { - if (name == null || name.isBlank()) { throw new IllegalArgumentException(ErrorMessage.NOT_ALLOW_EMPTY_INPUT.getMessage()); } } - public Deck getDeck() { - return deck; - } - - public String getName() { - return name; - } - - public abstract List getInitialVisibleCards(); - - public boolean isBust() { - return deck.isBust(); - } - - public int calculateDeckSum() { - return deck.calculateCardScoreSum(); - } - - public Optional addCard(Deck totalDeck) { - Card newCard = totalDeck.drawCard(); - this.deck.addCard(newCard); - return Optional.of(newCard); - } +// public String getName() { +// return name; +// } +// +//// public abstract List getInitialVisibleCards(); +// +// public boolean isBust() { +// return hand.isBust(); +// } +// +// public int calculateHandSum() { +// return hand.calculateCardScoreSum(); +// } +// +// public Optional addCard(Deck totalDeck) { +// Card newCard = totalDeck.drawCard(); +// this.hand.addCard(newCard); +// return Optional.of(newCard); +// } } \ No newline at end of file diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java index c528b14da20..48fec889a03 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/Player.java @@ -1,16 +1,7 @@ package domain; -import java.util.List; - public class Player extends Participant { - - public Player(Deck participantDeck, String name) { - super(participantDeck, name); + public Player(String name, Card card1, Card card2) { + super(name, card1, card2); } - - @Override - public List getInitialVisibleCards() { - return super.getDeck().getCards(); - } - } From e44a187a1c4b7a0805bbeabf2c0cf741c72454ba Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Tue, 10 Mar 2026 22:54:16 +0900 Subject: [PATCH 061/129] fix : change dealer consturctor of test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이전의 변경에 따른 생성자 로직 변경 --- src/test/java/domain/DealerTest.java | 117 ++++++++++++--------------- 1 file changed, 52 insertions(+), 65 deletions(-) diff --git a/src/test/java/domain/DealerTest.java b/src/test/java/domain/DealerTest.java index 2491c86489e..fe3485dd97c 100644 --- a/src/test/java/domain/DealerTest.java +++ b/src/test/java/domain/DealerTest.java @@ -2,10 +2,6 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import java.util.ArrayList; -import java.util.List; -import java.util.Optional; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -13,69 +9,60 @@ public class DealerTest { @Test @DisplayName("Dealer를 생성할 때 오류 발생 안함") void dealer_create_success() { - CardCreationStrategy fixedCardCreationStrategy = new CardCreationStrategy() { - @Override - public List create() { - Card spadeJ = new Card(CardShape.스페이드, CardContents.J); - Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); - - return new ArrayList<>(List.of(spadeJ, clover5)); - } - }; - Deck deck = Deck.createDeck(fixedCardCreationStrategy); - Deck participantDeck = Deck.createParticipantDeck(deck); + Card spadeJ = new Card(CardShape.스페이드, CardContents.J); + Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); assertDoesNotThrow( - () -> new Dealer(participantDeck) + () -> new Dealer(spadeJ, clover5) ); } - @Test - @DisplayName("딜러는 카드의 합이 16 이하면 카드를 한 장 더 받는다") - void addCard_Dealer_success() { - //given - CardCreationStrategy dealerCardCreationStrategy = () -> { - Card spadeJ = new Card(CardShape.스페이드, CardContents.J); - return new ArrayList<>(List.of(spadeJ)); - }; - CardCreationStrategy totalCardCreationStrategy = () -> { - Card spadeA = new Card(CardShape.스페이드, CardContents.A); - Card heartA = new Card(CardShape.하트, CardContents.TWO); - return new ArrayList<>(List.of(spadeA, heartA)); - }; - Deck dealerDeck = Deck.createDeck(dealerCardCreationStrategy); - Deck totalDeck = Deck.createDeck(totalCardCreationStrategy); - Dealer dealer = new Dealer(dealerDeck); - - //when - Optional result = dealer.addCard(totalDeck); - - //then - Assertions.assertThat(result.isPresent()).isTrue(); - } - - - @Test - @DisplayName("딜러는 카드의 합이 16 이상 -> Optional를 반환") - void addCard_Dealer_Optional_empty() { - //given - CardCreationStrategy dealerCardCreationStrategy = () -> { - Card spadeJ = new Card(CardShape.스페이드, CardContents.J); - Card heartJ = new Card(CardShape.하트, CardContents.J); - return new ArrayList<>(List.of(spadeJ, heartJ)); - }; - CardCreationStrategy totalCardCreationStrategy = () -> { - Card spadeA = new Card(CardShape.스페이드, CardContents.A); - Card heartA = new Card(CardShape.하트, CardContents.TWO); - return new ArrayList<>(List.of(spadeA, heartA)); - }; - Deck dealerDeck = Deck.createDeck(dealerCardCreationStrategy); - Deck totalDeck = Deck.createDeck(totalCardCreationStrategy); - Dealer dealer = new Dealer(dealerDeck); - - //when - Optional result = dealer.addCard(totalDeck); - - //then - Assertions.assertThat(result.isPresent()).isFalse(); - } +// @Test +// @DisplayName("딜러는 카드의 합이 16 이하면 카드를 한 장 더 받는다") +// void addCard_Dealer_success() { +// //given +// CardCreationStrategy dealerCardCreationStrategy = () -> { +// Card spadeJ = new Card(CardShape.스페이드, CardContents.J); +// return new ArrayList<>(List.of(spadeJ)); +// }; +// CardCreationStrategy totalCardCreationStrategy = () -> { +// Card spadeA = new Card(CardShape.스페이드, CardContents.A); +// Card heartA = new Card(CardShape.하트, CardContents.TWO); +// return new ArrayList<>(List.of(spadeA, heartA)); +// }; +// Deck dealerDeck = Deck.createDeck(dealerCardCreationStrategy); +// Deck totalDeck = Deck.createDeck(totalCardCreationStrategy); +// Dealer dealer = new Dealer(dealerDeck); +// +// //when +// Optional result = dealer.addCard(totalDeck); +// +// //then +// Assertions.assertThat(result.isPresent()).isTrue(); +// } +// +// +// @Test +// @DisplayName("딜러는 카드의 합이 16 이상 -> Optional를 반환") +// void addCard_Dealer_Optional_empty() { +// //given +// CardCreationStrategy dealerCardCreationStrategy = () -> { +// Card spadeJ = new Card(CardShape.스페이드, CardContents.J); +// Card heartJ = new Card(CardShape.하트, CardContents.J); +// return new ArrayList<>(List.of(spadeJ, heartJ)); +// }; +// CardCreationStrategy totalCardCreationStrategy = () -> { +// Card spadeA = new Card(CardShape.스페이드, CardContents.A); +// Card heartA = new Card(CardShape.하트, CardContents.TWO); +// return new ArrayList<>(List.of(spadeA, heartA)); +// }; +// Deck dealerDeck = Deck.createDeck(dealerCardCreationStrategy); +// Deck totalDeck = Deck.createDeck(totalCardCreationStrategy); +// Dealer dealer = new Dealer(dealerDeck); +// +// //when +// Optional result = dealer.addCard(totalDeck); +// +// //then +// Assertions.assertThat(result.isPresent()).isFalse(); +// } } From 97196d51948b3387f77921c9c9a9711cf85f7e93 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Tue, 10 Mar 2026 22:54:56 +0900 Subject: [PATCH 062/129] fix : change play constructor logic and add user uniqueness logic --- src/main/java/common/ErrorMessage.java | 1 + src/main/java/domain/Players.java | 53 +++++++++++++++----------- 2 files changed, 32 insertions(+), 22 deletions(-) diff --git a/src/main/java/common/ErrorMessage.java b/src/main/java/common/ErrorMessage.java index 25df3bb9232..bc21ba1e578 100644 --- a/src/main/java/common/ErrorMessage.java +++ b/src/main/java/common/ErrorMessage.java @@ -6,6 +6,7 @@ public enum ErrorMessage { UNSUPPORTED_OPERATION_MESSAGE("해당 객체[%s]에서는 지원하지 않는 메서드입니다 "), NOT_ALLOW_EMPTY_INPUT("공백은 허용되지 않습니다"), ONLY_KO_AND_ENG("이름은 영어 또는 한국어만 가능합니다: "), + NAME_UNIQUENESS_ERR("이름은 중복되면 안됩니다"), MAX_PLAYER_ERROR("최대 인원을 초과했습니다."); private final String message; diff --git a/src/main/java/domain/Players.java b/src/main/java/domain/Players.java index 61627868736..d8d50729847 100644 --- a/src/main/java/domain/Players.java +++ b/src/main/java/domain/Players.java @@ -1,10 +1,7 @@ package domain; import common.ErrorMessage; -import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; public class Players { private static final int MAX_PLAYER_NUMBER = 5; @@ -20,32 +17,44 @@ public static Players of(List playerNames, Deck totalDeck) { } private static List createPlayers(List playerNames, Deck totalDeck) { + validateUserCountLimit(playerNames); + validateNameUniqueness(playerNames); + + return playerNames.stream() + .map(name -> createNewPlayer(totalDeck, name)) + .toList(); + } + + private static Player createNewPlayer(Deck totalDeck, String name) { + List twoCards = totalDeck.drawTwoCards(); + return new Player(name, twoCards.get(0), twoCards.get(1)); + } + + private static void validateNameUniqueness(List playerNames) { + long uniqueCount = playerNames.stream().distinct().count(); + if (uniqueCount != playerNames.size()) { + throw new IllegalArgumentException(ErrorMessage.NAME_UNIQUENESS_ERR.toString()); + } + } + + private static void validateUserCountLimit(List playerNames) { if (playerNames.size() > MAX_PLAYER_NUMBER) { throw new IllegalArgumentException(ErrorMessage.MAX_PLAYER_ERROR.getMessage()); } - - List players = new ArrayList<>(); - playerNames.forEach( - name -> players.add( - new Player(Deck.createParticipantDeck(totalDeck), name) - ) - ); - - return players; } public List getPlayers() { return players; } - public Map> getDecksPerPlayer() { - Map> decksPerUser = new LinkedHashMap<>(); - for (Player player : players) { - decksPerUser.put( - player.getName(), - player.getDeck().getCards() - ); - } - return decksPerUser; - } +// public Map> getDecksPerPlayer() { +// Map> decksPerUser = new LinkedHashMap<>(); +// for (Player player : players) { +// decksPerUser.put( +// player.getName(), +// player.getDeck().getCards() +// ); +// } +// return decksPerUser; +// } } From dab5db98771a28df6d3edbda1fc45da38611dd8e Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Tue, 10 Mar 2026 23:04:28 +0900 Subject: [PATCH 063/129] fix : change construction test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 이전의 생성 변경에 따른 테스트 로직 변경 --- src/test/java/domain/PlayerTest.java | 68 ++++++++++++---------------- 1 file changed, 28 insertions(+), 40 deletions(-) diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java index 4b4e2978e49..3f105c4f5ab 100644 --- a/src/test/java/domain/PlayerTest.java +++ b/src/test/java/domain/PlayerTest.java @@ -2,9 +2,6 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import java.util.ArrayList; -import java.util.List; -import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -12,46 +9,37 @@ class PlayerTest { @Test @DisplayName("Player를 생성할 때 오류 발생 안함") void player_create_success() { - CardCreationStrategy fixedCardCreationStrategy = new CardCreationStrategy() { - @Override - public List create() { - Card spadeJ = new Card(CardShape.스페이드, CardContents.J); - Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); - - return new ArrayList<>(List.of(spadeJ, clover5)); - } - }; - Deck deck = Deck.createDeck(fixedCardCreationStrategy); - Deck participantDeck = Deck.createParticipantDeck(deck); + Card spadeJ = new Card(CardShape.스페이드, CardContents.J); + Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); String name = "pobi"; assertDoesNotThrow( - () -> new Player(participantDeck, name) + () -> new Player(name, spadeJ, clover5) ); } - - @Test - @DisplayName("플레이어가 카드를 한 장 더 받는다") - void addCardWhenSumBelowMinimum() { - //given - Card expectResultCard = new Card(CardShape.스페이드, CardContents.A); - CardCreationStrategy playerCardCreationStrategy = () -> { - Card spadeJ = new Card(CardShape.스페이드, CardContents.J); - return new ArrayList<>(List.of(spadeJ)); - }; - CardCreationStrategy totalCardCreationStrategy = () -> { - Card heartA = new Card(CardShape.하트, CardContents.TWO); - return new ArrayList<>(List.of(expectResultCard, heartA)); - }; - Deck playerDeck = Deck.createDeck(playerCardCreationStrategy); - String testPlayerName = "pobi"; - Deck totalDeck = Deck.createDeck(totalCardCreationStrategy); - Player player = new Player(playerDeck, testPlayerName); - - //when - Card resultCard = player.addCard(totalDeck).get(); - - //then - Assertions.assertThat(resultCard).isEqualTo(expectResultCard); - } +// +// @Test +// @DisplayName("플레이어가 카드를 한 장 더 받는다") +// void addCardWhenSumBelowMinimum() { +// //given +// Card expectResultCard = new Card(CardShape.스페이드, CardContents.A); +// CardCreationStrategy playerCardCreationStrategy = () -> { +// Card spadeJ = new Card(CardShape.스페이드, CardContents.J); +// return new ArrayList<>(List.of(spadeJ)); +// }; +// CardCreationStrategy totalCardCreationStrategy = () -> { +// Card heartA = new Card(CardShape.하트, CardContents.TWO); +// return new ArrayList<>(List.of(expectResultCard, heartA)); +// }; +// Deck playerDeck = Deck.createDeck(playerCardCreationStrategy); +// String testPlayerName = "pobi"; +// Deck totalDeck = Deck.createDeck(totalCardCreationStrategy); +// Player player = new Player(playerDeck, testPlayerName); +// +// //when +// Card resultCard = player.addCard(totalDeck).get(); +// +// //then +// Assertions.assertThat(resultCard).isEqualTo(expectResultCard); +// } } \ No newline at end of file From 1caf04553cde8bcadb96e88c22d3cf22fb4bb982 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Tue, 10 Mar 2026 23:05:05 +0900 Subject: [PATCH 064/129] test : add duplication logic test code and change constructor of players at test --- src/test/java/domain/PlayersTest.java | 76 +++++++++++++++++---------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/src/test/java/domain/PlayersTest.java b/src/test/java/domain/PlayersTest.java index 1e6f10b64ea..97aff9f6a5f 100644 --- a/src/test/java/domain/PlayersTest.java +++ b/src/test/java/domain/PlayersTest.java @@ -1,13 +1,16 @@ package domain; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertEquals; +import common.ErrorMessage; +import java.util.ArrayDeque; import java.util.ArrayList; +import java.util.Deque; import java.util.List; -import java.util.Map; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; class PlayersTest { @@ -20,37 +23,52 @@ void setUpTotalDeck() { totalDeck = Deck.createDeck(totalCardCreationStrategy); } - @Test - @DisplayName("생성 잘 한다") - void of_good() { - //given - List testPlayerNames = List.of("pobi", "terry", "rati", "gump"); + @Nested + class ofTest { + @Test + @DisplayName("생성 잘 한다") + void of_good() { + //given + List testPlayerNames = List.of("pobi", "terry", "rati", "gump"); - //when, then - assertDoesNotThrow( - () -> Players.of(testPlayerNames, totalDeck) - ); - } - - @Test - @DisplayName("getDecksPerUser에서 잘 가져온다") - void getDecksPerUser_success() { - //given - List testPlayerNames = List.of("pobi"); - List expectPobiCards = List.of( - new Card(CardShape.스페이드, CardContents.A), - new Card(CardShape.스페이드, CardContents.TWO) - ); + //when, then + assertDoesNotThrow( + () -> Players.of(testPlayerNames, totalDeck) + ); + } - //when - Players players = Players.of(testPlayerNames, totalDeck); - Map> result = players.getDecksPerPlayer(); + @Test + @DisplayName("이름이 중복되면 오류가 발생한다") + void of_fail_duplication() { + //given + List testPlayerNames = List.of("pobi", "pobi"); - //then - assertEquals(expectPobiCards, result.get("pobi")); + //when && then + assertThatThrownBy(() -> Players.of(testPlayerNames, totalDeck)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ErrorMessage.NAME_UNIQUENESS_ERR.getMessage()); + } } - private List createSampleCards() { + // @Test +// @DisplayName("getDecksPerUser에서 잘 가져온다") +// void getDecksPerUser_success() { +// //given +// List testPlayerNames = List.of("pobi"); +// List expectPobiCards = List.of( +// new Card(CardShape.스페이드, CardContents.A), +// new Card(CardShape.스페이드, CardContents.TWO) +// ); +// +// //when +// Players players = Players.of(testPlayerNames, totalDeck); +// Map> result = players.getDecksPerPlayer(); +// +// //then +// assertEquals(expectPobiCards, result.get("pobi")); +// } +// + private Deque createSampleCards() { CardShape[] shapes = CardShape.values(); CardContents[] contents = CardContents.values(); @@ -61,7 +79,7 @@ private List createSampleCards() { } } - return sampleCards; + return new ArrayDeque<>(sampleCards); } } From 0fc9a8fed36fa6067e8058815ea8a061bfec686a Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 09:31:19 +0900 Subject: [PATCH 065/129] fix : add isBust method that extracted from Deck --- src/main/java/domain/Hand.java | 14 ++++++++------ src/test/java/domain/HandTest.java | 28 ++++++++++++++++++++++++++++ 2 files changed, 36 insertions(+), 6 deletions(-) create mode 100644 src/test/java/domain/HandTest.java diff --git a/src/main/java/domain/Hand.java b/src/main/java/domain/Hand.java index 46baac4cdcf..b696a39aea8 100644 --- a/src/main/java/domain/Hand.java +++ b/src/main/java/domain/Hand.java @@ -15,13 +15,12 @@ public static Hand of(Card card1, Card card2) { return new Hand(List.of(card1, card2)); } - //사용자 카드 보관함을 생성해줘!! - - public void addCard(Card card) { - this.cards.add(card); + //TODO : 책임 : 가지고 있는 카드패가 isBust 인지 판단하세요. + public boolean isBust() { + return calculateCardScoreSum() > BUST_CRITERIA; } - public int calculateCardScoreSum() { + private int calculateCardScoreSum() { int sumExceptAce = calculateCardScoreSumExceptAce(); int sumAce = AceScoreDiscriminator.calculateAceCardsSum(cards, sumExceptAce); @@ -33,7 +32,6 @@ private int calculateCardScoreSumExceptAce() { for (Card card : cards) { sum = addCardScoreExceptAce(card, sum); } - return sum; } @@ -43,4 +41,8 @@ private int addCardScoreExceptAce(Card card, int sum) { } return sum; } + +// public void addCard(Card card) { +// this.cards.add(card); +// } } \ No newline at end of file diff --git a/src/test/java/domain/HandTest.java b/src/test/java/domain/HandTest.java new file mode 100644 index 00000000000..42bb2aade5b --- /dev/null +++ b/src/test/java/domain/HandTest.java @@ -0,0 +1,28 @@ +package domain; + +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class HandTest { + + @Nested + class isBustTest { + @Test + @DisplayName("카드 합이 21 이하이면 isBust는 False다") + void isBust_False() { + //given + Hand testHand = Hand.of( + new Card(CardShape.다이아몬드, CardContents.K), + new Card(CardShape.다이아몬드, CardContents.J) + ); + + //when + boolean result = testHand.isBust(); + + //then + Assertions.assertFalse(result); + } + } +} \ No newline at end of file From 1dc6cc33db2cacc96630c91a813f3171edf68bb3 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 09:46:59 +0900 Subject: [PATCH 066/129] fix : add addCard method that extract from Deck --- src/main/java/domain/Hand.java | 14 +++++++++----- src/test/java/domain/HandTest.java | 31 +++++++++++++++++++++++------- 2 files changed, 33 insertions(+), 12 deletions(-) diff --git a/src/main/java/domain/Hand.java b/src/main/java/domain/Hand.java index b696a39aea8..59f7627d5be 100644 --- a/src/main/java/domain/Hand.java +++ b/src/main/java/domain/Hand.java @@ -1,6 +1,7 @@ package domain; import java.util.ArrayList; +import java.util.Collections; import java.util.List; public class Hand { @@ -15,11 +16,18 @@ public static Hand of(Card card1, Card card2) { return new Hand(List.of(card1, card2)); } - //TODO : 책임 : 가지고 있는 카드패가 isBust 인지 판단하세요. public boolean isBust() { return calculateCardScoreSum() > BUST_CRITERIA; } + public List showCards() { + return Collections.unmodifiableList(cards); + } + + public void addCard(Card card) { + this.cards.add(card); + } + private int calculateCardScoreSum() { int sumExceptAce = calculateCardScoreSumExceptAce(); int sumAce = AceScoreDiscriminator.calculateAceCardsSum(cards, sumExceptAce); @@ -41,8 +49,4 @@ private int addCardScoreExceptAce(Card card, int sum) { } return sum; } - -// public void addCard(Card card) { -// this.cards.add(card); -// } } \ No newline at end of file diff --git a/src/test/java/domain/HandTest.java b/src/test/java/domain/HandTest.java index 42bb2aade5b..b0799bc1f9d 100644 --- a/src/test/java/domain/HandTest.java +++ b/src/test/java/domain/HandTest.java @@ -7,22 +7,39 @@ class HandTest { + Hand testHandThatValueFive = Hand.of( + new Card(CardShape.다이아몬드, CardContents.TWO), + new Card(CardShape.다이아몬드, CardContents.THREE) + ); + @Nested class isBustTest { @Test @DisplayName("카드 합이 21 이하이면 isBust는 False다") void isBust_False() { - //given - Hand testHand = Hand.of( - new Card(CardShape.다이아몬드, CardContents.K), - new Card(CardShape.다이아몬드, CardContents.J) - ); - //when - boolean result = testHand.isBust(); + boolean result = testHandThatValueFive.isBust(); //then Assertions.assertFalse(result); } + + //TODO : isBust_False 의 결과를 21로 차후 수정. + //TODO: isBust_True 테스트 추가 + } + + @Test + @DisplayName("받은 카드 1장을 잘 추가한다") + void addCard_success() { + //given + Card testCard = new Card(CardShape.하트, CardContents.SEVEN); + int beforeAddCardSize = testHandThatValueFive.showCards().size(); + + //when + testHandThatValueFive.addCard(testCard); + + //then + int afterAddCardSize = testHandThatValueFive.showCards().size(); + Assertions.assertTrue(beforeAddCardSize + 1 == afterAddCardSize); } } \ No newline at end of file From 1ff3368e4602dba447774b78b1a2e5596c3204e5 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 09:51:30 +0900 Subject: [PATCH 067/129] test : add addCard test and fix isBust test logic to use boundary value --- src/test/java/domain/HandTest.java | 28 ++++++++++++++++++++++++++-- 1 file changed, 26 insertions(+), 2 deletions(-) diff --git a/src/test/java/domain/HandTest.java b/src/test/java/domain/HandTest.java index b0799bc1f9d..d30bb20d9f2 100644 --- a/src/test/java/domain/HandTest.java +++ b/src/test/java/domain/HandTest.java @@ -1,5 +1,6 @@ package domain; +import java.util.List; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -17,6 +18,14 @@ class isBustTest { @Test @DisplayName("카드 합이 21 이하이면 isBust는 False다") void isBust_False() { + List cardsThatValue16 = List.of( + new Card(CardShape.다이아몬드, CardContents.SIX), + new Card(CardShape.다이아몬드, CardContents.TEN) + ); + cardsThatValue16.forEach( + card -> testHandThatValueFive.addCard(card) + ); + //when boolean result = testHandThatValueFive.isBust(); @@ -24,8 +33,23 @@ void isBust_False() { Assertions.assertFalse(result); } - //TODO : isBust_False 의 결과를 21로 차후 수정. - //TODO: isBust_True 테스트 추가 + @Test + @DisplayName("카드 합이 21 초과이면 isBust는 True다") + void isBust_True() { + List cardsThatValue17 = List.of( + new Card(CardShape.다이아몬드, CardContents.SEVEN), + new Card(CardShape.다이아몬드, CardContents.TEN) + ); + cardsThatValue17.forEach( + card -> testHandThatValueFive.addCard(card) + ); + + //when + boolean result = testHandThatValueFive.isBust(); + + //then + Assertions.assertTrue(result); + } } @Test From 26e608aad9ac5ad25f4f5da98cda663895c34690 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 13:15:47 +0900 Subject: [PATCH 068/129] fix : change position of hit logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit hit를 할 수 있고 없음에 따라 히트를 수행하는 책임의 가장 적합한 주인이 Player인 것 같아 위치 변경 --- src/main/java/domain/Player.java | 13 +++++++ src/test/java/domain/PlayerTest.java | 55 +++++++++++++++++++++++++++- 2 files changed, 66 insertions(+), 2 deletions(-) diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java index 48fec889a03..c55361e0d28 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/Player.java @@ -1,7 +1,20 @@ package domain; +import java.util.function.Supplier; + public class Player extends Participant { public Player(String name, Card card1, Card card2) { super(name, card1, card2); } + + public void hit(Supplier cardSupplier) { + Hand ownHand = this.hand; + if (canHit(ownHand)) { + ownHand.addCard(cardSupplier.get()); + } + } + + private boolean canHit(Hand ownHand) { + return !ownHand.isBust(); + } } diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java index 3f105c4f5ab..85936366f3c 100644 --- a/src/test/java/domain/PlayerTest.java +++ b/src/test/java/domain/PlayerTest.java @@ -2,7 +2,13 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import java.util.LinkedList; +import java.util.List; +import java.util.Queue; +import java.util.function.Supplier; +import org.assertj.core.api.Assertions; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; class PlayerTest { @@ -10,14 +16,59 @@ class PlayerTest { @DisplayName("Player를 생성할 때 오류 발생 안함") void player_create_success() { Card spadeJ = new Card(CardShape.스페이드, CardContents.J); - Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); + Card clover5 = new Card(CardShape.클로버, CardContents.K); String name = "pobi"; assertDoesNotThrow( () -> new Player(name, spadeJ, clover5) ); } -// + + @Nested + class hitTest { + //given + String testName = "gump"; + Player testPlayer = new Player( + testName, + new Card(CardShape.스페이드, CardContents.J), + new Card(CardShape.클로버, CardContents.FIVE) + ); + + @Test + @DisplayName("hit 할 수 있는 상태이면 hit를 진행한다") + void hit_do() { + //given + Queue testDeck = new LinkedList<>(List.of( + new Card(CardShape.클로버, CardContents.SEVEN) + )); + Supplier testCardSupplier = () -> testDeck.poll(); + + //when + testPlayer.hit(testCardSupplier); + + //then + Assertions.assertThat(testCardSupplier.get()).isNull(); + } + + @Test + @DisplayName("hit 할 수 없는 상태이면 hit를 진행하지 않는다") + void hit_do_not() { + //given + Queue testDeck = new LinkedList<>(List.of( + new Card(CardShape.하트, CardContents.TEN), + new Card(CardShape.클로버, CardContents.TEN) + )); + Supplier testCardSupplier = () -> testDeck.poll(); + testPlayer.hit(testCardSupplier); + + //when + testPlayer.hit(testCardSupplier); + + //then + Assertions.assertThat(testCardSupplier.get()).isNotNull(); + } + + } // @Test // @DisplayName("플레이어가 카드를 한 장 더 받는다") // void addCardWhenSumBelowMinimum() { From 5de2106adcebf9e7147d2125e7f7ec852f54b83f Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 15:13:22 +0900 Subject: [PATCH 069/129] fix : add change turn logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 Game에서 반복문으로 관리하던 다음 사용자를 정하고, turn을 직을 Players 에서 하는 것이 더욱 적절할 것으로 판단되어 분리 후 추가 --- src/main/java/domain/Players.java | 21 +++++++++++++++++++-- src/test/java/domain/PlayersTest.java | 22 ++++++++++++++++++---- 2 files changed, 37 insertions(+), 6 deletions(-) diff --git a/src/main/java/domain/Players.java b/src/main/java/domain/Players.java index d8d50729847..ca7738c9cde 100644 --- a/src/main/java/domain/Players.java +++ b/src/main/java/domain/Players.java @@ -2,11 +2,13 @@ import common.ErrorMessage; import java.util.List; +import java.util.Optional; public class Players { private static final int MAX_PLAYER_NUMBER = 5; private final List players; + private int orderCursor = 0; private Players(List players) { this.players = players; @@ -43,10 +45,25 @@ private static void validateUserCountLimit(List playerNames) { } } - public List getPlayers() { - return players; + public Optional findCurrentUser() { + if (this.orderCursor < this.players.size()) { + return Optional.of(players.get(orderCursor)); + } + return Optional.empty(); + } + + public void next() { + orderCursor++; } +// public void hitPlayer(Player targetPlayer, Supplier cardSupplier) { +// targetPlayer.hit(cardSupplier); +// } + +// public List getPlayers() { +// return players; +// } + // public Map> getDecksPerPlayer() { // Map> decksPerUser = new LinkedHashMap<>(); // for (Player player : players) { diff --git a/src/test/java/domain/PlayersTest.java b/src/test/java/domain/PlayersTest.java index 97aff9f6a5f..6326a0dcdee 100644 --- a/src/test/java/domain/PlayersTest.java +++ b/src/test/java/domain/PlayersTest.java @@ -8,12 +8,15 @@ import java.util.ArrayList; import java.util.Deque; import java.util.List; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; class PlayersTest { + private static final List TEST_PLAYER_NAMES = List.of("pobi", "terry", "rati", "gump"); + Deck totalDeck; @@ -28,12 +31,9 @@ class ofTest { @Test @DisplayName("생성 잘 한다") void of_good() { - //given - List testPlayerNames = List.of("pobi", "terry", "rati", "gump"); - //when, then assertDoesNotThrow( - () -> Players.of(testPlayerNames, totalDeck) + () -> Players.of(TEST_PLAYER_NAMES, totalDeck) ); } @@ -50,6 +50,20 @@ void of_fail_duplication() { } } + @Test + @DisplayName("다음 사용자로 넘기라는 명령을 받은 이후 현재 사용자 호출 시 다른 사용자가 나오다") + void next_change_targetUser() { + //given + Players testPlayers = Players.of(TEST_PLAYER_NAMES, totalDeck); + Player prevPlayer = testPlayers.findCurrentUser().get(); + + //when + testPlayers.next(); + + //then + Assertions.assertNotEquals(prevPlayer, testPlayers.findCurrentUser()); + } + // @Test // @DisplayName("getDecksPerUser에서 잘 가져온다") // void getDecksPerUser_success() { From 0dce66a627b5119817b1d82253a48aa06ca3344d Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 15:35:28 +0900 Subject: [PATCH 070/129] fix : separate responsibility MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit players가 사용자의 stay 여부를 판별하는 것보다 player가 각자의 stay 여부를 판별하는 것이 적절하다고 판단하여 수정 --- src/main/java/domain/Dealer.java | 4 ++-- src/main/java/domain/Participant.java | 8 ++++---- src/main/java/domain/Player.java | 17 ++++++++++++++--- src/main/java/domain/Players.java | 2 +- src/test/java/domain/DealerTest.java | 9 ++++++--- src/test/java/domain/PlayerTest.java | 17 ++++++++++------- 6 files changed, 37 insertions(+), 20 deletions(-) diff --git a/src/main/java/domain/Dealer.java b/src/main/java/domain/Dealer.java index 5d19c295eeb..970e9c428cf 100644 --- a/src/main/java/domain/Dealer.java +++ b/src/main/java/domain/Dealer.java @@ -4,8 +4,8 @@ public class Dealer extends Participant { private static final int MINIMUM_TOTAL_SCORE = 16; private static final String DEALER_NAME = "딜러"; - public Dealer(Card card1, Card card2) { - super(DEALER_NAME, card1, card2); + public Dealer(Hand hand) { + super(DEALER_NAME, hand); } // @Override diff --git a/src/main/java/domain/Participant.java b/src/main/java/domain/Participant.java index 1ae37951372..618bb693be1 100644 --- a/src/main/java/domain/Participant.java +++ b/src/main/java/domain/Participant.java @@ -4,11 +4,11 @@ import java.util.regex.Pattern; public abstract class Participant { - private final Hand hand; - private final String name; + protected final Hand hand; + protected final String name; - protected Participant(String name, Card card1, Card card2) { - this.hand = Hand.of(card1, card2); + protected Participant(String name, Hand hand) { + this.hand = hand; validateName(name); this.name = name; } diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java index c55361e0d28..34472474afe 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/Player.java @@ -3,8 +3,19 @@ import java.util.function.Supplier; public class Player extends Participant { - public Player(String name, Card card1, Card card2) { - super(name, card1, card2); + private final boolean isStay; + + private Player(String name, Hand hand, boolean isStay) { + super(name, hand); + this.isStay = isStay; + } + + public static Player from(String name, Hand hand) { + return new Player(name, hand, false); + } + + public Player stay() { + return new Player(this.name, this.hand, true); } public void hit(Supplier cardSupplier) { @@ -17,4 +28,4 @@ public void hit(Supplier cardSupplier) { private boolean canHit(Hand ownHand) { return !ownHand.isBust(); } -} +} \ No newline at end of file diff --git a/src/main/java/domain/Players.java b/src/main/java/domain/Players.java index ca7738c9cde..0a1c03af74d 100644 --- a/src/main/java/domain/Players.java +++ b/src/main/java/domain/Players.java @@ -29,7 +29,7 @@ private static List createPlayers(List playerNames, Deck totalDe private static Player createNewPlayer(Deck totalDeck, String name) { List twoCards = totalDeck.drawTwoCards(); - return new Player(name, twoCards.get(0), twoCards.get(1)); + return Player.from(name, Hand.of(twoCards.get(0), twoCards.get(1))); } private static void validateNameUniqueness(List playerNames) { diff --git a/src/test/java/domain/DealerTest.java b/src/test/java/domain/DealerTest.java index fe3485dd97c..c34cf078e74 100644 --- a/src/test/java/domain/DealerTest.java +++ b/src/test/java/domain/DealerTest.java @@ -9,10 +9,13 @@ public class DealerTest { @Test @DisplayName("Dealer를 생성할 때 오류 발생 안함") void dealer_create_success() { - Card spadeJ = new Card(CardShape.스페이드, CardContents.J); - Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); + Hand dealerHand = Hand.of( + new Card(CardShape.스페이드, CardContents.J), + new Card(CardShape.클로버, CardContents.FIVE) + ); + assertDoesNotThrow( - () -> new Dealer(spadeJ, clover5) + () -> new Dealer(dealerHand) ); } diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java index 85936366f3c..dce664eeeb9 100644 --- a/src/test/java/domain/PlayerTest.java +++ b/src/test/java/domain/PlayerTest.java @@ -15,24 +15,27 @@ class PlayerTest { @Test @DisplayName("Player를 생성할 때 오류 발생 안함") void player_create_success() { - Card spadeJ = new Card(CardShape.스페이드, CardContents.J); - Card clover5 = new Card(CardShape.클로버, CardContents.K); + Hand playerHand = Hand.of( + new Card(CardShape.스페이드, CardContents.J), + new Card(CardShape.클로버, CardContents.K) + ); + String name = "pobi"; assertDoesNotThrow( - () -> new Player(name, spadeJ, clover5) + () -> Player.from(name, playerHand) ); } @Nested class hitTest { //given - String testName = "gump"; - Player testPlayer = new Player( - testName, + Hand playerHand = Hand.of( new Card(CardShape.스페이드, CardContents.J), new Card(CardShape.클로버, CardContents.FIVE) ); + String testName = "gump"; + Player testPlayer = Player.from(testName, playerHand); @Test @DisplayName("hit 할 수 있는 상태이면 hit를 진행한다") @@ -60,7 +63,7 @@ void hit_do_not() { )); Supplier testCardSupplier = () -> testDeck.poll(); testPlayer.hit(testCardSupplier); - + //when testPlayer.hit(testCardSupplier); From 042e6eccfc97e7af44b7a5528ed898789f9cb68f Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 15:47:42 +0900 Subject: [PATCH 071/129] fix : add test logic of isFinished MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Player에서 추가된 상태를 관리하는 메서드인 isFinished를 관리하기 위한 test 추가 --- src/main/java/domain/Player.java | 4 +++ src/test/java/domain/PlayerTest.java | 40 ++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+) diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java index 34472474afe..c6c6d4366cf 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/Player.java @@ -18,6 +18,10 @@ public Player stay() { return new Player(this.name, this.hand, true); } + public boolean isFinished() { + return this.hand.isBust() || isStay; + } + public void hit(Supplier cardSupplier) { Hand ownHand = this.hand; if (canHit(ownHand)) { diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java index dce664eeeb9..c934d46828f 100644 --- a/src/test/java/domain/PlayerTest.java +++ b/src/test/java/domain/PlayerTest.java @@ -70,7 +70,47 @@ void hit_do_not() { //then Assertions.assertThat(testCardSupplier.get()).isNotNull(); } + } + + @Test + @DisplayName("stay를 호출하면 새로운 사용자를 반환하고 그 사용자는 게임을 종료한 상태가 된다") + void stay_and_finish() { + //given + Hand playerHand = Hand.of( + new Card(CardShape.스페이드, CardContents.J), + new Card(CardShape.클로버, CardContents.FIVE) + ); + String testName = "gump"; + Player testPlayer = Player.from(testName, playerHand); + + //when + testPlayer = testPlayer.stay(); + + //then + Assertions.assertThat(testPlayer.isFinished()).isTrue(); + } + + @Test + @DisplayName("bust가 되어도 해당 사용자는 게임을 종료한 상태가 된다") + void bust_and_finish() { + //given + Queue testDeck = new LinkedList<>(List.of( + new Card(CardShape.클로버, CardContents.SEVEN) + )); + + Hand playerHand = Hand.of( + new Card(CardShape.스페이드, CardContents.J), + new Card(CardShape.클로버, CardContents.FIVE) + ); + String testName = "gump"; + Player testPlayer = Player.from(testName, playerHand); + testPlayer.hit(() -> testDeck.poll()); + + //when + testPlayer = testPlayer.stay(); + //then + Assertions.assertThat(testPlayer.isFinished()).isTrue(); } // @Test // @DisplayName("플레이어가 카드를 한 장 더 받는다") From b147e7f4c9b5aa88a82fb63dc7c92d9f840fe1ff Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 15:52:50 +0900 Subject: [PATCH 072/129] fix : change find Next Player logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 사용자가 게임의 참가 상태를 소유하게 됨에 따라 다음 사용자를 찾는 로직을 변경 --- src/main/java/domain/Players.java | 12 +++--------- src/test/java/domain/PlayersTest.java | 8 +++----- 2 files changed, 6 insertions(+), 14 deletions(-) diff --git a/src/main/java/domain/Players.java b/src/main/java/domain/Players.java index 0a1c03af74d..1346672ce94 100644 --- a/src/main/java/domain/Players.java +++ b/src/main/java/domain/Players.java @@ -8,7 +8,6 @@ public class Players { private static final int MAX_PLAYER_NUMBER = 5; private final List players; - private int orderCursor = 0; private Players(List players) { this.players = players; @@ -46,14 +45,9 @@ private static void validateUserCountLimit(List playerNames) { } public Optional findCurrentUser() { - if (this.orderCursor < this.players.size()) { - return Optional.of(players.get(orderCursor)); - } - return Optional.empty(); - } - - public void next() { - orderCursor++; + return players.stream() + .filter(player -> !player.isFinished()) + .findFirst(); } // public void hitPlayer(Player targetPlayer, Supplier cardSupplier) { diff --git a/src/test/java/domain/PlayersTest.java b/src/test/java/domain/PlayersTest.java index 6326a0dcdee..6a4fb48863f 100644 --- a/src/test/java/domain/PlayersTest.java +++ b/src/test/java/domain/PlayersTest.java @@ -51,16 +51,14 @@ void of_fail_duplication() { } @Test - @DisplayName("다음 사용자로 넘기라는 명령을 받은 이후 현재 사용자 호출 시 다른 사용자가 나오다") + @DisplayName("사용자의 상태에 따라 Turn이 변경된다") void next_change_targetUser() { //given Players testPlayers = Players.of(TEST_PLAYER_NAMES, totalDeck); Player prevPlayer = testPlayers.findCurrentUser().get(); + prevPlayer = prevPlayer.stay(); - //when - testPlayers.next(); - - //then + //when, then Assertions.assertNotEquals(prevPlayer, testPlayers.findCurrentUser()); } From 70793a94abb78dd774e11690ab3de3c96f7ae3f1 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 16:11:17 +0900 Subject: [PATCH 073/129] fix : delete duplicated logic --- src/main/java/domain/Player.java | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java index c6c6d4366cf..959614d8ed3 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/Player.java @@ -18,18 +18,13 @@ public Player stay() { return new Player(this.name, this.hand, true); } - public boolean isFinished() { - return this.hand.isBust() || isStay; - } - public void hit(Supplier cardSupplier) { - Hand ownHand = this.hand; - if (canHit(ownHand)) { - ownHand.addCard(cardSupplier.get()); + if (!isFinished()) { + hand.addCard(cardSupplier.get()); } } - private boolean canHit(Hand ownHand) { - return !ownHand.isBust(); + public boolean isFinished() { + return hand.isBust() || isStay; } } \ No newline at end of file From 528b5d7d59c7c949bfeb741902c2c6f55bdde6e4 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 16:29:31 +0900 Subject: [PATCH 074/129] feat : override eqauls of Player MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 차후 플레이어를 교체하기 위한 equals override 수행 --- src/main/java/domain/Player.java | 18 ++++++++++++++++++ src/main/java/domain/Players.java | 7 +++++++ src/test/java/domain/PlayerTest.java | 19 +++++++++++++++++++ 3 files changed, 44 insertions(+) diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java index 959614d8ed3..d745bf76639 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/Player.java @@ -1,5 +1,6 @@ package domain; +import java.util.Objects; import java.util.function.Supplier; public class Player extends Participant { @@ -27,4 +28,21 @@ public void hit(Supplier cardSupplier) { public boolean isFinished() { return hand.isBust() || isStay; } + + @Override + public boolean equals(Object o) { + if (this == o) { + return true; + } + if (!(o instanceof Player player)) { + return false; + } + + return Objects.equals(getName(), player.getName()); + } + + @Override + public int hashCode() { + return Objects.hash(getName()); + } } \ No newline at end of file diff --git a/src/main/java/domain/Players.java b/src/main/java/domain/Players.java index 1346672ce94..6514c697203 100644 --- a/src/main/java/domain/Players.java +++ b/src/main/java/domain/Players.java @@ -50,6 +50,13 @@ public Optional findCurrentUser() { .findFirst(); } + public void updateToStand(Player player) { + int index = players.indexOf(player); + if (index != -1) { + players.set(index, player.stay()); + } + } + // public void hitPlayer(Player targetPlayer, Supplier cardSupplier) { // targetPlayer.hit(cardSupplier); // } diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java index c934d46828f..e27b71da193 100644 --- a/src/test/java/domain/PlayerTest.java +++ b/src/test/java/domain/PlayerTest.java @@ -112,6 +112,25 @@ void bust_and_finish() { //then Assertions.assertThat(testPlayer.isFinished()).isTrue(); } + + @Test + @DisplayName("이름이 같으면 같은 Player로 본다") + void equal_when_name_equal() { + Hand playerHand1 = Hand.of( + new Card(CardShape.스페이드, CardContents.A), + new Card(CardShape.클로버, CardContents.TWO) + ); + Hand playerHand2 = Hand.of( + new Card(CardShape.하트, CardContents.THREE), + new Card(CardShape.클로버, CardContents.FOUR) + ); + String testName = "gump"; + Player testPlayer1 = Player.from(testName, playerHand1); + Player testPlayer2 = Player.from(testName, playerHand2); + + //when, then + Assertions.assertThat(testPlayer1.equals(testPlayer2)).isTrue(); + } // @Test // @DisplayName("플레이어가 카드를 한 장 더 받는다") // void addCardWhenSumBelowMinimum() { From 247e5fe7bba37e9749840fab9f55b604f5a765d3 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 16:59:20 +0900 Subject: [PATCH 075/129] fix : add stand logic in players MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 관리하는 사용자를 stand 처리하도록 하는 로직을 추가 --- src/main/java/domain/Players.java | 7 +++++-- src/test/java/domain/PlayersTest.java | 23 +++++++++++++++++++++++ 2 files changed, 28 insertions(+), 2 deletions(-) diff --git a/src/main/java/domain/Players.java b/src/main/java/domain/Players.java index 6514c697203..10a9028c3f0 100644 --- a/src/main/java/domain/Players.java +++ b/src/main/java/domain/Players.java @@ -1,6 +1,7 @@ package domain; import common.ErrorMessage; +import java.util.ArrayList; import java.util.List; import java.util.Optional; @@ -14,7 +15,9 @@ private Players(List players) { } public static Players of(List playerNames, Deck totalDeck) { - return new Players(createPlayers(playerNames, totalDeck)); + return new Players( + new ArrayList<>(createPlayers(playerNames, totalDeck)) + ); } private static List createPlayers(List playerNames, Deck totalDeck) { @@ -50,7 +53,7 @@ public Optional findCurrentUser() { .findFirst(); } - public void updateToStand(Player player) { + public void stand(Player player) { int index = players.indexOf(player); if (index != -1) { players.set(index, player.stay()); diff --git a/src/test/java/domain/PlayersTest.java b/src/test/java/domain/PlayersTest.java index 6a4fb48863f..257885785dd 100644 --- a/src/test/java/domain/PlayersTest.java +++ b/src/test/java/domain/PlayersTest.java @@ -1,5 +1,6 @@ package domain; +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; @@ -8,6 +9,7 @@ import java.util.ArrayList; import java.util.Deque; import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -62,6 +64,27 @@ void next_change_targetUser() { Assertions.assertNotEquals(prevPlayer, testPlayers.findCurrentUser()); } + @Test + @DisplayName("사용자가 stay가 되면 더 이상 현재 사용자로 조회되지 않는다") + void stand_success() { + // given + String rati = "rati"; + String pobi = "pobi"; + Players players = Players.of(List.of(rati, pobi), totalDeck); + Player currentPlayer = players.findCurrentUser().get(); + + // when + players.stand(currentPlayer); + + // then + Optional nextUser = players.findCurrentUser(); + assertThat(nextUser.isPresent()).isTrue(); + assertThat(nextUser.get().getName()).isEqualTo(pobi); + + players.stand(nextUser.get()); + assertThat(players.findCurrentUser()).isEmpty(); + } + // @Test // @DisplayName("getDecksPerUser에서 잘 가져온다") // void getDecksPerUser_success() { From bfa4055e8e90daa886f7247b8cf2fa4b39196bfd Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 17:28:51 +0900 Subject: [PATCH 076/129] fix : delete unnecessary feature MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 사용자 덱과 전체 덱을 분리하면서 필요없게 된 addCard 메소드 제거 --- src/main/java/domain/Deck.java | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/main/java/domain/Deck.java b/src/main/java/domain/Deck.java index c409fed5313..1e64c6ef503 100644 --- a/src/main/java/domain/Deck.java +++ b/src/main/java/domain/Deck.java @@ -33,9 +33,4 @@ public List drawTwoCards() { throw new IllegalArgumentException(ErrorMessage.DRAW_CARD_OUT_OF_RANGE.getMessage()); } } - - public int addCard(Card card) { - this.cards.add(card); - return cards.size(); - } } From 72c323a9bdf2035a10ec1d42fcf93be08b627578 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 17:58:16 +0900 Subject: [PATCH 077/129] fix : change hand to immutable object MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit player는 불변인데, player가 가지고 있는 hand 가 불변이 아니어서 생기는 헷갈림을 없애고자 hand를 불변으로 변경 --- src/main/java/domain/Hand.java | 16 +++++-- src/test/java/domain/HandTest.java | 36 +++++++++------ src/test/java/domain/PlayerTest.java | 68 ++++++++++++++++++++-------- 3 files changed, 82 insertions(+), 38 deletions(-) diff --git a/src/main/java/domain/Hand.java b/src/main/java/domain/Hand.java index 59f7627d5be..7df4fea4daa 100644 --- a/src/main/java/domain/Hand.java +++ b/src/main/java/domain/Hand.java @@ -9,7 +9,7 @@ public class Hand { private final List cards; private Hand(List cards) { - this.cards = new ArrayList<>(cards); + this.cards = cards; } public static Hand of(Card card1, Card card2) { @@ -20,12 +20,18 @@ public boolean isBust() { return calculateCardScoreSum() > BUST_CRITERIA; } + public boolean isFull() { + return calculateCardScoreSum() == BUST_CRITERIA; + } + public List showCards() { return Collections.unmodifiableList(cards); } - public void addCard(Card card) { - this.cards.add(card); + public Hand addCard(Card card) { + ArrayList mutableCards = new ArrayList<>(cards); + mutableCards.add(card); + return new Hand(mutableCards); } private int calculateCardScoreSum() { @@ -38,12 +44,12 @@ private int calculateCardScoreSum() { private int calculateCardScoreSumExceptAce() { int sum = 0; for (Card card : cards) { - sum = addCardScoreExceptAce(card, sum); + sum = calculateCardScoreSumWithoutAce(card, sum); } return sum; } - private int addCardScoreExceptAce(Card card, int sum) { + private int calculateCardScoreSumWithoutAce(Card card, int sum) { if (!card.isAce()) { sum += card.getCardContents().getScore(); } diff --git a/src/test/java/domain/HandTest.java b/src/test/java/domain/HandTest.java index d30bb20d9f2..e2e8c217683 100644 --- a/src/test/java/domain/HandTest.java +++ b/src/test/java/domain/HandTest.java @@ -8,23 +8,24 @@ class HandTest { - Hand testHandThatValueFive = Hand.of( - new Card(CardShape.다이아몬드, CardContents.TWO), - new Card(CardShape.다이아몬드, CardContents.THREE) - ); - @Nested class isBustTest { @Test @DisplayName("카드 합이 21 이하이면 isBust는 False다") void isBust_False() { + Hand testHandThatValueFive = Hand.of( + new Card(CardShape.다이아몬드, CardContents.TWO), + new Card(CardShape.다이아몬드, CardContents.THREE) + ); + List cardsThatValue16 = List.of( new Card(CardShape.다이아몬드, CardContents.SIX), new Card(CardShape.다이아몬드, CardContents.TEN) ); - cardsThatValue16.forEach( - card -> testHandThatValueFive.addCard(card) - ); + + for (Card card : cardsThatValue16) { + testHandThatValueFive = testHandThatValueFive.addCard(card); + } //when boolean result = testHandThatValueFive.isBust(); @@ -36,13 +37,18 @@ void isBust_False() { @Test @DisplayName("카드 합이 21 초과이면 isBust는 True다") void isBust_True() { + Hand testHandThatValueFive = Hand.of( + new Card(CardShape.다이아몬드, CardContents.TWO), + new Card(CardShape.다이아몬드, CardContents.THREE) + ); List cardsThatValue17 = List.of( new Card(CardShape.다이아몬드, CardContents.SEVEN), new Card(CardShape.다이아몬드, CardContents.TEN) ); - cardsThatValue17.forEach( - card -> testHandThatValueFive.addCard(card) - ); + + for (Card card : cardsThatValue17) { + testHandThatValueFive = testHandThatValueFive.addCard(card); + } //when boolean result = testHandThatValueFive.isBust(); @@ -56,14 +62,18 @@ void isBust_True() { @DisplayName("받은 카드 1장을 잘 추가한다") void addCard_success() { //given + Hand testHandThatValueFive = Hand.of( + new Card(CardShape.다이아몬드, CardContents.TWO), + new Card(CardShape.다이아몬드, CardContents.THREE) + ); Card testCard = new Card(CardShape.하트, CardContents.SEVEN); int beforeAddCardSize = testHandThatValueFive.showCards().size(); //when - testHandThatValueFive.addCard(testCard); + testHandThatValueFive = testHandThatValueFive.addCard(testCard); //then int afterAddCardSize = testHandThatValueFive.showCards().size(); - Assertions.assertTrue(beforeAddCardSize + 1 == afterAddCardSize); + Assertions.assertEquals(beforeAddCardSize + 1, afterAddCardSize); } } \ No newline at end of file diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java index e27b71da193..afde68257a7 100644 --- a/src/test/java/domain/PlayerTest.java +++ b/src/test/java/domain/PlayerTest.java @@ -1,6 +1,7 @@ package domain; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertFalse; import java.util.LinkedList; import java.util.List; @@ -74,7 +75,7 @@ void hit_do_not() { @Test @DisplayName("stay를 호출하면 새로운 사용자를 반환하고 그 사용자는 게임을 종료한 상태가 된다") - void stay_and_finish() { + void stand_and_finish() { //given Hand playerHand = Hand.of( new Card(CardShape.스페이드, CardContents.J), @@ -84,33 +85,60 @@ void stay_and_finish() { Player testPlayer = Player.from(testName, playerHand); //when - testPlayer = testPlayer.stay(); + testPlayer = testPlayer.stand(); //then Assertions.assertThat(testPlayer.isFinished()).isTrue(); } - @Test - @DisplayName("bust가 되어도 해당 사용자는 게임을 종료한 상태가 된다") - void bust_and_finish() { - //given - Queue testDeck = new LinkedList<>(List.of( - new Card(CardShape.클로버, CardContents.SEVEN) - )); + @Nested + class HitClass { - Hand playerHand = Hand.of( - new Card(CardShape.스페이드, CardContents.J), - new Card(CardShape.클로버, CardContents.FIVE) - ); - String testName = "gump"; - Player testPlayer = Player.from(testName, playerHand); - testPlayer.hit(() -> testDeck.poll()); + @Test + @DisplayName("hit를 잘 수행하고, 완료가 아닌 상태를 유지한다") + void hit_and_not_finished() { + //given + Queue testDeck = new LinkedList<>(List.of( + new Card(CardShape.하트, CardContents.TEN) + )); - //when - testPlayer = testPlayer.stay(); + Hand playerHand = Hand.of( + new Card(CardShape.스페이드, CardContents.TEN), + new Card(CardShape.클로버, CardContents.TEN) + ); + String testName = "gump"; + Player testPlayer = Player.from(testName, playerHand); - //then - Assertions.assertThat(testPlayer.isFinished()).isTrue(); + //when + testPlayer = testPlayer.hit(testDeck::poll); + + //then + assertFalse(testPlayer.isFinished()); + } + + @Test + @DisplayName("hit를 요구했지만 bust이면 해당 사용자는 게임을 종료한 상태가 된다") + void hit_butBust_and_finish() { + //given + Queue testDeck = new LinkedList<>(List.of( + new Card(CardShape.하트, CardContents.TEN), + new Card(CardShape.클로버, CardContents.A) + )); + + Hand playerHand = Hand.of( + new Card(CardShape.스페이드, CardContents.TEN), + new Card(CardShape.클로버, CardContents.TEN) + ); + String testName = "gump"; + Player testPlayer = Player.from(testName, playerHand); + testPlayer.hit(testDeck::poll); + + //when + testPlayer = testPlayer.hit(testDeck::poll); + + //then + Assertions.assertThat(testPlayer.isFinished()).isTrue(); + } } @Test From 8ae329333a82c01ca86f9d5657f5c906a955f46a Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 18:31:02 +0900 Subject: [PATCH 078/129] fix : change logic via method name changing --- src/test/java/domain/PlayersTest.java | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/test/java/domain/PlayersTest.java b/src/test/java/domain/PlayersTest.java index 257885785dd..5b52b6344cb 100644 --- a/src/test/java/domain/PlayersTest.java +++ b/src/test/java/domain/PlayersTest.java @@ -57,32 +57,32 @@ void of_fail_duplication() { void next_change_targetUser() { //given Players testPlayers = Players.of(TEST_PLAYER_NAMES, totalDeck); - Player prevPlayer = testPlayers.findCurrentUser().get(); - prevPlayer = prevPlayer.stay(); + Player prevPlayer = testPlayers.findNotStayPlayer().get(); + prevPlayer = prevPlayer.stand(); //when, then - Assertions.assertNotEquals(prevPlayer, testPlayers.findCurrentUser()); + Assertions.assertNotEquals(prevPlayer, testPlayers.findNotStayPlayer()); } @Test @DisplayName("사용자가 stay가 되면 더 이상 현재 사용자로 조회되지 않는다") - void stand_success() { + void executeStand_success() { // given String rati = "rati"; String pobi = "pobi"; Players players = Players.of(List.of(rati, pobi), totalDeck); - Player currentPlayer = players.findCurrentUser().get(); + Player currentPlayer = players.findNotStayPlayer().get(); // when - players.stand(currentPlayer); + players.executeStand(currentPlayer); // then - Optional nextUser = players.findCurrentUser(); + Optional nextUser = players.findNotStayPlayer(); assertThat(nextUser.isPresent()).isTrue(); assertThat(nextUser.get().getName()).isEqualTo(pobi); - players.stand(nextUser.get()); - assertThat(players.findCurrentUser()).isEmpty(); + players.executeStand(nextUser.get()); + assertThat(players.findNotStayPlayer()).isEmpty(); } // @Test From 41c71750205bf607b92e0cd12193a5498db94d81 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 18:31:54 +0900 Subject: [PATCH 079/129] fix : change assertion logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Hand가 불변객체로 변함에 따라 검증 방법 변경 --- src/test/java/domain/PlayerTest.java | 32 +++++++++++++--------------- 1 file changed, 15 insertions(+), 17 deletions(-) diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java index afde68257a7..4f44d816917 100644 --- a/src/test/java/domain/PlayerTest.java +++ b/src/test/java/domain/PlayerTest.java @@ -1,6 +1,7 @@ package domain; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertFalse; import java.util.LinkedList; @@ -93,51 +94,48 @@ void stand_and_finish() { @Nested class HitClass { + Queue onlyTenCardsDeck = new LinkedList<>(List.of( + new Card(CardShape.하트, CardContents.TEN), + new Card(CardShape.하트, CardContents.J), + new Card(CardShape.하트, CardContents.K), + new Card(CardShape.하트, CardContents.Q) + )); @Test - @DisplayName("hit를 잘 수행하고, 완료가 아닌 상태를 유지한다") + @DisplayName("hit를 잘 수행한다") void hit_and_not_finished() { //given - Queue testDeck = new LinkedList<>(List.of( - new Card(CardShape.하트, CardContents.TEN) - )); - Hand playerHand = Hand.of( - new Card(CardShape.스페이드, CardContents.TEN), - new Card(CardShape.클로버, CardContents.TEN) + new Card(CardShape.스페이드, CardContents.A), + new Card(CardShape.클로버, CardContents.TWO) ); String testName = "gump"; Player testPlayer = Player.from(testName, playerHand); //when - testPlayer = testPlayer.hit(testDeck::poll); + testPlayer = testPlayer.hit(onlyTenCardsDeck::poll); //then assertFalse(testPlayer.isFinished()); } @Test - @DisplayName("hit를 요구했지만 bust이면 해당 사용자는 게임을 종료한 상태가 된다") + @DisplayName("hit를 요구했지만 bust 상태이면 변화가 없다") void hit_butBust_and_finish() { //given - Queue testDeck = new LinkedList<>(List.of( - new Card(CardShape.하트, CardContents.TEN), - new Card(CardShape.클로버, CardContents.A) - )); - Hand playerHand = Hand.of( new Card(CardShape.스페이드, CardContents.TEN), new Card(CardShape.클로버, CardContents.TEN) ); String testName = "gump"; Player testPlayer = Player.from(testName, playerHand); - testPlayer.hit(testDeck::poll); + testPlayer.hit(onlyTenCardsDeck::poll); //when - testPlayer = testPlayer.hit(testDeck::poll); + Player hitResultPlayer = testPlayer.hit(onlyTenCardsDeck::poll); //then - Assertions.assertThat(testPlayer.isFinished()).isTrue(); + assertEquals(testPlayer.hashCode(), hitResultPlayer.hashCode()); } } From 8066d08683642b948b9a54cf57f9b6d417dc7f2c Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 18:32:58 +0900 Subject: [PATCH 080/129] fix : change method name MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 이해가 쉽도록 메서드 이름 변경 - getString으로 오류 수정 --- src/main/java/domain/Players.java | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/src/main/java/domain/Players.java b/src/main/java/domain/Players.java index 10a9028c3f0..df140776832 100644 --- a/src/main/java/domain/Players.java +++ b/src/main/java/domain/Players.java @@ -4,6 +4,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Optional; +import java.util.function.Supplier; public class Players { private static final int MAX_PLAYER_NUMBER = 5; @@ -37,7 +38,7 @@ private static Player createNewPlayer(Deck totalDeck, String name) { private static void validateNameUniqueness(List playerNames) { long uniqueCount = playerNames.stream().distinct().count(); if (uniqueCount != playerNames.size()) { - throw new IllegalArgumentException(ErrorMessage.NAME_UNIQUENESS_ERR.toString()); + throw new IllegalArgumentException(ErrorMessage.NAME_UNIQUENESS_ERR.getMessage()); } } @@ -47,19 +48,25 @@ private static void validateUserCountLimit(List playerNames) { } } - public Optional findCurrentUser() { + public Optional findNotStayPlayer() { return players.stream() .filter(player -> !player.isFinished()) .findFirst(); } - public void stand(Player player) { + public void executeHit(Player player, Supplier cardSupplier) { int index = players.indexOf(player); if (index != -1) { - players.set(index, player.stay()); + players.set(index, player.hit(cardSupplier)); } } + public void executeStand(Player player) { + int index = players.indexOf(player); + if (index != -1) { + players.set(index, player.stand()); + } + } // public void hitPlayer(Player targetPlayer, Supplier cardSupplier) { // targetPlayer.hit(cardSupplier); // } From e7e2bec514ebff20e649341572cf4d8e2f857905 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 18:33:35 +0900 Subject: [PATCH 081/129] fix : change return type of method hit and rename some method --- src/main/java/domain/Player.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java index d745bf76639..f5b9ffb33b8 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/Player.java @@ -15,18 +15,21 @@ public static Player from(String name, Hand hand) { return new Player(name, hand, false); } - public Player stay() { + public Player stand() { return new Player(this.name, this.hand, true); } - public void hit(Supplier cardSupplier) { - if (!isFinished()) { - hand.addCard(cardSupplier.get()); + public Player hit(Supplier cardSupplier) { + if (isFinished()) { + return this; } + + Hand newHand = hand.addCard(cardSupplier.get()); + return new Player(this.name, newHand, false); } public boolean isFinished() { - return hand.isBust() || isStay; + return hand.isBust() || hand.isFull() || isStay; } @Override From bcee50492aa65a7dd9aeedbe4f36def2a767b826 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 18:34:16 +0900 Subject: [PATCH 082/129] fix : commennt out getname method --- src/main/java/domain/Participant.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/java/domain/Participant.java b/src/main/java/domain/Participant.java index 618bb693be1..aefec1c3912 100644 --- a/src/main/java/domain/Participant.java +++ b/src/main/java/domain/Participant.java @@ -31,9 +31,9 @@ private static void validateIsNotBlank(String name) { } } -// public String getName() { -// return name; -// } + public String getName() { + return name; + } // //// public abstract List getInitialVisibleCards(); // From 48ac237ece38d6afe4fc2b07a89d4771e1786987 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 18:34:43 +0900 Subject: [PATCH 083/129] feat : add ErrorMessage of PLAYER_NOT_FOUND --- src/main/java/common/ErrorMessage.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/common/ErrorMessage.java b/src/main/java/common/ErrorMessage.java index bc21ba1e578..abe240b9bb0 100644 --- a/src/main/java/common/ErrorMessage.java +++ b/src/main/java/common/ErrorMessage.java @@ -7,6 +7,7 @@ public enum ErrorMessage { NOT_ALLOW_EMPTY_INPUT("공백은 허용되지 않습니다"), ONLY_KO_AND_ENG("이름은 영어 또는 한국어만 가능합니다: "), NAME_UNIQUENESS_ERR("이름은 중복되면 안됩니다"), + PLAYER_NOT_FOUND("해당 플레이어를 찾을 수 없습니다"), MAX_PLAYER_ERROR("최대 인원을 초과했습니다."); private final String message; From bff9364403289f50418b00672adbe0036d3910b0 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 18:38:54 +0900 Subject: [PATCH 084/129] fix : update showCards logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 기존 Deck에서 Hand로 변경됨에 따른 내부 구현 로직 변경 --- src/main/java/dto/PlayerDto.java | 9 +++++++++ 1 file changed, 9 insertions(+) create mode 100644 src/main/java/dto/PlayerDto.java diff --git a/src/main/java/dto/PlayerDto.java b/src/main/java/dto/PlayerDto.java new file mode 100644 index 00000000000..8f62658b6bd --- /dev/null +++ b/src/main/java/dto/PlayerDto.java @@ -0,0 +1,9 @@ +package dto; + +import java.util.List; + +public record PlayerDto( + String name, + List cards +) { +} From 3bc71611d09d06de512dd7be2a31df15caea7833 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 19:04:25 +0900 Subject: [PATCH 085/129] fix : move showCards method in Participant and implement that at child of --- src/main/java/domain/Dealer.java | 8 ++++++++ src/main/java/domain/Participant.java | 7 +++++++ src/main/java/domain/Player.java | 6 ++++++ 3 files changed, 21 insertions(+) diff --git a/src/main/java/domain/Dealer.java b/src/main/java/domain/Dealer.java index 970e9c428cf..3407ae3ca2f 100644 --- a/src/main/java/domain/Dealer.java +++ b/src/main/java/domain/Dealer.java @@ -1,5 +1,7 @@ package domain; +import java.util.List; + public class Dealer extends Participant { private static final int MINIMUM_TOTAL_SCORE = 16; private static final String DEALER_NAME = "딜러"; @@ -8,6 +10,12 @@ public Dealer(Hand hand) { super(DEALER_NAME, hand); } + @Override + public List showInitialCard() { + List ownCards = super.showOwnCards(); + return List.of(ownCards.getFirst()); + } + // @Override // public Optional addCard(Deck totalDeck) { // if (super.calculateHandSum() <= MINIMUM_TOTAL_SCORE) { diff --git a/src/main/java/domain/Participant.java b/src/main/java/domain/Participant.java index aefec1c3912..c38391813b6 100644 --- a/src/main/java/domain/Participant.java +++ b/src/main/java/domain/Participant.java @@ -1,6 +1,7 @@ package domain; import common.ErrorMessage; +import java.util.List; import java.util.regex.Pattern; public abstract class Participant { @@ -34,6 +35,12 @@ private static void validateIsNotBlank(String name) { public String getName() { return name; } + + public List showOwnCards() { + return hand.showCards(); + } + + public abstract List showInitialCard(); // //// public abstract List getInitialVisibleCards(); // diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java index f5b9ffb33b8..66297244318 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/Player.java @@ -1,5 +1,6 @@ package domain; +import java.util.List; import java.util.Objects; import java.util.function.Supplier; @@ -32,6 +33,11 @@ public boolean isFinished() { return hand.isBust() || hand.isFull() || isStay; } + @Override + public List showInitialCard() { + return super.showOwnCards(); + } + @Override public boolean equals(Object o) { if (this == o) { From 4c0c499a2567f50e558aac6641d9056c936b85be Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 19:05:57 +0900 Subject: [PATCH 086/129] fix : comment out print initial state logic and rename of those MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 최초의 결과를 출력하기 위한 기능을 다시 작동시키기 위해 주석을 해제. 이때 메서드 이름에 상대방의 Cards 와 같은 내용이 들어가서 State로 추상화하였음 --- src/main/java/domain/Players.java | 26 +++-- src/main/java/dto/ParticipantDto.java | 19 ++-- src/main/java/view/OutputView.java | 158 +++++++++++++------------- 3 files changed, 102 insertions(+), 101 deletions(-) diff --git a/src/main/java/domain/Players.java b/src/main/java/domain/Players.java index df140776832..b93b8ccd8de 100644 --- a/src/main/java/domain/Players.java +++ b/src/main/java/domain/Players.java @@ -1,8 +1,11 @@ package domain; import common.ErrorMessage; +import dto.ParticipantDto; import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.function.Supplier; @@ -67,6 +70,12 @@ public void executeStand(Player player) { players.set(index, player.stand()); } } + + public List getInitialStates() { + return players.stream() + .map(ParticipantDto::consistWithInitialInfo) + .toList(); + } // public void hitPlayer(Player targetPlayer, Supplier cardSupplier) { // targetPlayer.hit(cardSupplier); // } @@ -75,14 +84,11 @@ public void executeStand(Player player) { // return players; // } -// public Map> getDecksPerPlayer() { -// Map> decksPerUser = new LinkedHashMap<>(); -// for (Player player : players) { -// decksPerUser.put( -// player.getName(), -// player.getDeck().getCards() -// ); -// } -// return decksPerUser; -// } + public Map> getDecksPerPlayer() { + Map> decksPerUser = new LinkedHashMap<>(); + for (Player player : players) { + decksPerUser.put(player.getName(), player.showOwnCards()); + } + return decksPerUser; + } } diff --git a/src/main/java/dto/ParticipantDto.java b/src/main/java/dto/ParticipantDto.java index fd5b4b2d481..e3a4a6fcaef 100644 --- a/src/main/java/dto/ParticipantDto.java +++ b/src/main/java/dto/ParticipantDto.java @@ -5,29 +5,28 @@ import java.util.ArrayList; import java.util.List; -public record ParticipantDto(String name, List cards, int score) { - public static ParticipantDto initialFrom(Participant participant) { +public record ParticipantDto(String name, List cards) { + public static ParticipantDto consistWithInitialInfo(Participant participant) { String name = participant.getName(); List cards = new ArrayList<>(); - for (Card card : participant.getInitialVisibleCards()) { + for (Card card : participant.showInitialCard()) { cards.add(CardDto.from(card)); } int score = 0; - return new ParticipantDto(name, cards, score); + return new ParticipantDto(name, cards); } public static ParticipantDto from(Participant participant) { String name = participant.getName(); List cards = new ArrayList<>(); - for (Card card : participant.getDeck().getCards()) { + for (Card card : participant.showOwnCards()) { cards.add(CardDto.from(card)); } - int score = participant.calculateDeckSum(); - return new ParticipantDto(name, cards, score); + return new ParticipantDto(name, cards); } public static List listOf(List participants) { @@ -37,13 +36,11 @@ public static List listOf(List participan String name = participant.getName(); List cards = new ArrayList<>(); - for (Card card : participant.getDeck().getCards()) { + for (Card card : participant.showOwnCards()) { cards.add(CardDto.from(card)); } - int score = participant.calculateDeckSum(); - - ParticipantDto participantDto = new ParticipantDto(name, cards, score); + ParticipantDto participantDto = new ParticipantDto(name, cards); participantDtos.add(participantDto); } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 05df1b6b18c..4592aa33d8f 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,12 +1,9 @@ package view; import dto.CardDto; -import dto.GameResultDto; import dto.ParticipantDto; import java.util.ArrayList; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; public class OutputView { private static final String DELIMITER = ", "; @@ -28,102 +25,103 @@ public void printNamePrompt() { System.out.println(NAME_PROMPT); } - public void printInitialCardShareDetail(ParticipantDto dealerDto, List players) { + public void printInitialStates(ParticipantDto dealerDto, List players) { List playerNames = new ArrayList<>(); - StringBuilder initialCardShareDetail = new StringBuilder(); + StringBuilder initialStates = new StringBuilder(); - String dealerCardInfo = consistCardInfo(PARTICIPANT_CARD_INFO_FORMAT_LINE, dealerDto); - initialCardShareDetail.append(dealerCardInfo); + String dealerCardInfo = consistCardInfo(dealerDto); + initialStates.append(dealerCardInfo); for (ParticipantDto playerDto : players) { playerNames.add(playerDto.name()); - String playerCardInfo = consistCardInfo(PARTICIPANT_CARD_INFO_FORMAT_LINE, playerDto); - initialCardShareDetail.append(playerCardInfo); + String playerCardInfo = consistCardInfo(playerDto); + initialStates.append(playerCardInfo); } System.out.println(); System.out.printf(INITIAL_CARD_SHARE, String.join(DELIMITER, playerNames)); - System.out.println(initialCardShareDetail); + System.out.println(initialStates); } public void printHitOrStandPrompt(String name) { System.out.printf(HIT_OR_STAND_PROMPT, name); } - public void printUserCardInfo(ParticipantDto participantDto) { - String userCardInfo = consistCardInfo(PARTICIPANT_CARD_INFO_FORMAT_LINE, participantDto); - System.out.println(userCardInfo); - } - - public void printAdditionalCardForDealerDescription() { - System.out.println(); - System.out.println(ADDITIONAL_CARD_FOR_DEALER_DESCRIPTION); - } - - public void printCardInfoWithSum(GameResultDto gameResultDto) { - StringBuilder cardInfoWithSum = new StringBuilder(); - - ParticipantDto dealerDto = gameResultDto.dealerDto(); - String dealerCardInfoWithSum = consistCardInfoWithSum(dealerDto); - cardInfoWithSum.append(dealerCardInfoWithSum); - - for (ParticipantDto playerDto : gameResultDto.playerDtos()) { - String playerCardInfoWithSum = consistCardInfoWithSum(playerDto); - cardInfoWithSum.append(playerCardInfoWithSum); - } - - System.out.println(); - System.out.println(cardInfoWithSum); - } - - public void printWinLossResult(GameResultDto gameResultDto) { - StringBuilder winLossResult = new StringBuilder(); - winLossResult.append(WIN_LOSS_RESULT_HEADER); - - String dealerResult = consistDealerResult(gameResultDto); - winLossResult.append(dealerResult); - - String playerResults = consistPlayerResults(gameResultDto); - winLossResult.append(playerResults); - - System.out.println(winLossResult); - } - - private String consistCardInfo(String infoFormat, ParticipantDto participantDto) { + // +// public void printUserCardInfo(ParticipantDto participantDto) { +// String userCardInfo = consistCardInfo(PARTICIPANT_CARD_INFO_FORMAT_LINE, participantDto); +// System.out.println(userCardInfo); +// } +// +// public void printAdditionalCardForDealerDescription() { +// System.out.println(); +// System.out.println(ADDITIONAL_CARD_FOR_DEALER_DESCRIPTION); +// } +// +// public void printCardInfoWithSum(GameResultDto gameResultDto) { +// StringBuilder cardInfoWithSum = new StringBuilder(); +// +// ParticipantDto dealerDto = gameResultDto.dealerDto(); +// String dealerCardInfoWithSum = consistCardInfoWithSum(dealerDto); +// cardInfoWithSum.append(dealerCardInfoWithSum); +// +// for (ParticipantDto playerDto : gameResultDto.playerDtos()) { +// String playerCardInfoWithSum = consistCardInfoWithSum(playerDto); +// cardInfoWithSum.append(playerCardInfoWithSum); +// } +// +// System.out.println(); +// System.out.println(cardInfoWithSum); +// } +// +// public void printWinLossResult(GameResultDto gameResultDto) { +// StringBuilder winLossResult = new StringBuilder(); +// winLossResult.append(WIN_LOSS_RESULT_HEADER); +// +// String dealerResult = consistDealerResult(gameResultDto); +// winLossResult.append(dealerResult); +// +// String playerResults = consistPlayerResults(gameResultDto); +// winLossResult.append(playerResults); +// +// System.out.println(winLossResult); +// } +// + private String consistCardInfo(ParticipantDto participantDto) { List cardInfos = new ArrayList<>(); for (CardDto card : participantDto.cards()) { cardInfos.add(card.cardContentNumber() + card.cardShape()); } - return String.format(infoFormat, participantDto.name(), + return String.format(OutputView.PARTICIPANT_CARD_INFO_FORMAT_LINE, participantDto.name(), String.join(DELIMITER, cardInfos)); } - - private String consistCardInfoWithSum(ParticipantDto participantDto) { - return String.format(PARTICIPANT_CARD_INFO_WITH_SUM_FORMAT, - consistCardInfo(PARTICIPANT_CARD_INFO_FORMAT, participantDto), participantDto.score()); - } - - private String consistDealerResult(GameResultDto gameResultDto) { - String dealerName = gameResultDto.dealerDto().name(); - Map dealerWinLossResults = gameResultDto.dealerWinLossResults(); - StringBuilder result = new StringBuilder(); - for (Entry dealerResult : dealerWinLossResults.entrySet()) { - result.append(dealerResult.getValue()); - result.append(dealerResult.getKey()); - result.append(" "); - } - result.deleteCharAt(result.length() - 1); - return String.format(WIN_LOSS_RESULT_FORMAT, dealerName, result); - } - - private String consistPlayerResults(GameResultDto gameResultDto) { - Map playerWinLossResults = gameResultDto.playerWinLossResults(); - StringBuilder result = new StringBuilder(); - for (Entry playerResult : playerWinLossResults.entrySet()) { - String playerName = playerResult.getKey(); - String playerWinLossResult = playerResult.getValue(); - result.append(String.format(WIN_LOSS_RESULT_FORMAT, playerName, playerWinLossResult)); - } - return result.toString(); - } +// +// private String consistCardInfoWithSum(ParticipantDto participantDto) { +// return String.format(PARTICIPANT_CARD_INFO_WITH_SUM_FORMAT, +// consistCardInfo(PARTICIPANT_CARD_INFO_FORMAT, participantDto), participantDto.score()); +// } +// +// private String consistDealerResult(GameResultDto gameResultDto) { +// String dealerName = gameResultDto.dealerDto().name(); +// Map dealerWinLossResults = gameResultDto.dealerWinLossResults(); +// StringBuilder result = new StringBuilder(); +// for (Entry dealerResult : dealerWinLossResults.entrySet()) { +// result.append(dealerResult.getValue()); +// result.append(dealerResult.getKey()); +// result.append(" "); +// } +// result.deleteCharAt(result.length() - 1); +// return String.format(WIN_LOSS_RESULT_FORMAT, dealerName, result); +// } +// +// private String consistPlayerResults(GameResultDto gameResultDto) { +// Map playerWinLossResults = gameResultDto.playerWinLossResults(); +// StringBuilder result = new StringBuilder(); +// for (Entry playerResult : playerWinLossResults.entrySet()) { +// String playerName = playerResult.getKey(); +// String playerWinLossResult = playerResult.getValue(); +// result.append(String.format(WIN_LOSS_RESULT_FORMAT, playerName, playerWinLossResult)); +// } +// return result.toString(); +// } } From ff92b24bddfa37498c0d46af59eeb7bc9d40535d Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 19:07:22 +0900 Subject: [PATCH 087/129] fix : comment out setting linkers of this program MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 해당 프로그램에서 게임 세팅이라는 큰 역할을 담당하는 곳들의 주석을 해제하여 다시 연결을 시도. 일부 변경된 메서드 명을 변경 --- src/main/java/Main.java | 2 +- .../java/controller/BlackJackController.java | 89 +++---- src/main/java/domain/BlackJackGame.java | 120 +++++++++ .../controller/BlackJackControllerTest.java | 238 +++++++++--------- 4 files changed, 272 insertions(+), 177 deletions(-) create mode 100644 src/main/java/domain/BlackJackGame.java diff --git a/src/main/java/Main.java b/src/main/java/Main.java index 8b594a94ee4..99b27e98929 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -10,6 +10,6 @@ public static void main(String[] args) { OutputView outputView = new OutputView(); CardCreationStrategy strategy = new RandomCardCreationStrategy(); - new BlackJackController(inputView, outputView, strategy).doGame(); + new BlackJackController(inputView, outputView, strategy).doGameProcess(); } } diff --git a/src/main/java/controller/BlackJackController.java b/src/main/java/controller/BlackJackController.java index da40810ffe1..e7ca3631136 100644 --- a/src/main/java/controller/BlackJackController.java +++ b/src/main/java/controller/BlackJackController.java @@ -1,16 +1,16 @@ package controller; +import domain.BlackJackGame; import domain.CardCreationStrategy; -import domain.Game; -import dto.GameResultDto; -import dto.ParticipantDto; +import domain.Player; import java.util.List; -import java.util.function.BiFunction; -import java.util.function.Consumer; +import java.util.Optional; +import java.util.function.Supplier; import view.InputView; import view.OutputView; -public class BlackJackController implements GameDelegate { +// 게임의 총 흐름을 담당. +public class BlackJackController { private final InputView inputView; private final OutputView outputView; private final CardCreationStrategy strategy; @@ -21,61 +21,44 @@ public BlackJackController(InputView inputView, OutputView outputView, CardCreat this.strategy = strategy; } - public void doGame() { - Game game = retry(Game::ready, this, strategy); - retry(game::play, this); - game.end(this); - } - - @Override - public List askPlayerNames() { - outputView.printNamePrompt(); - return inputView.readNames(); - } - - @Override - public boolean askDrawCard(String playerName) { - outputView.printHitOrStandPrompt(playerName); - String input = inputView.readHitOrStand(); - return input.equals("y"); - } - - @Override - public void showInitialParticipantCards(ParticipantDto dealerDto, List playerDtos) { - outputView.printInitialCardShareDetail(dealerDto, playerDtos); - } - - @Override - public void showPlayerCards(ParticipantDto participantDto) { - outputView.printUserCardInfo(participantDto); - } - - @Override - public void showDealerOneMoreCardMessage() { - outputView.printAdditionalCardForDealerDescription(); - } - - @Override - public void showGameResult(GameResultDto resultDto) { - outputView.printCardInfoWithSum(resultDto); - outputView.printWinLossResult(resultDto); - } + public void doGameProcess() { + BlackJackGame game = retry(this::readyGame); + outputView.printInitialStates( + game.getDealerGameSettingResult(), + game.getPlayersGameSettingResults() + ); - private R retry(BiFunction biFunction, T input1, U input2) { + //2. 게임을 진행하라 while (true) { - try { - return biFunction.apply(input1, input2); - } catch (IllegalArgumentException e) { - outputView.printErrorMessage(e); + Optional currentPlayer = game.whoseTurn(); + if (currentPlayer.isEmpty()) { + break; + } + + String currentUserName = currentPlayer.get().getName(); + outputView.printHitOrStandPrompt(currentUserName); + String hitOrStand = inputView.readHitOrStand(); + if (hitOrStand.equals("y")) { + game.doHitProcess(); //TODO: 출력이 야기되어야 합니다. + } else { + game.doStandProcess(); } } +// +// //3. 게임 결과를 구하라 +// blackJackGame.end(this); + } + + private BlackJackGame readyGame() { + outputView.printNamePrompt(); + List playerNames = inputView.readNames(); + return BlackJackGame.ready(playerNames, strategy); } - private void retry(Consumer consumer, T input) { + private T retry(Supplier supplier) { while (true) { try { - consumer.accept(input); - return; + return supplier.get(); } catch (IllegalArgumentException e) { outputView.printErrorMessage(e); } diff --git a/src/main/java/domain/BlackJackGame.java b/src/main/java/domain/BlackJackGame.java new file mode 100644 index 00000000000..76f372cc29d --- /dev/null +++ b/src/main/java/domain/BlackJackGame.java @@ -0,0 +1,120 @@ +package domain; + +import dto.ParticipantDto; +import java.util.List; +import java.util.Optional; + +public class BlackJackGame { + private final Deck totalDeck; + private final Dealer dealer; + private final Players players; + + private BlackJackGame(Deck totalDeck, Dealer dealer, Players players) { + this.totalDeck = totalDeck; + this.dealer = dealer; + this.players = players; + } + + public static BlackJackGame ready(List playerNames, CardCreationStrategy strategy) { + Deck totalDeck = Deck.createDeck(strategy); + + Dealer dealer = createNewDealer(totalDeck); + Players players = Players.of(playerNames, totalDeck); + + return new BlackJackGame(totalDeck, dealer, players); + } + + private static Dealer createNewDealer(Deck totalDeck) { + List dealersInitialCards = totalDeck.drawTwoCards(); + Card card1 = dealersInitialCards.get(0); + Card card2 = dealersInitialCards.get(1); + return new Dealer(Hand.of(card1, card2)); + } + + public Optional whoseTurn() { + return players.findNotStayPlayer(); + } + + public void doHitProcess() { + players.findNotStayPlayer() + .ifPresent(player -> players.executeHit(player, totalDeck::drawCard)); + } + + public void doStandProcess() { + players.findNotStayPlayer() + .ifPresent(players::executeStand); + } + + public List getPlayersGameSettingResults() { + return players.getInitialStates(); + } + + public ParticipantDto getDealerGameSettingResult() { + return ParticipantDto.consistWithInitialInfo(dealer); + } +// +// public void play(GameDelegate observer) { +// List individualPlayers = players.getPlayers(); +// for (Player player : individualPlayers) { +// while (!player.isBust() && observer.askDrawCard(player.getName())) { +// player.addCard(totalDeck); +// observer.showPlayerCards(ParticipantDto.from(player)); +// } +// } +// while (dealer.addCard(totalDeck).isPresent()) { +// observer.showDealerOneMoreCardMessage(); +// } +// } +// +// public void end(GameDelegate delegate) { +// GameResultDto result = this.calculateResult(); +// delegate.showGameResult(result); +// } +// +// private GameResultDto calculateResult() { +// int dealerScore = dealer.calculateHandSum(); +// boolean isDealerBust = dealer.isBust(); +// +// Map playerWinLossResults = consistPlayerWinLossResults(dealerScore, isDealerBust); +// Map dealerWinLossResults = consistDealerResult(playerWinLossResults); +// +// return GameResultDto.from( +// dealer, +// players, +// dealerWinLossResults, +// playerWinLossResults +// ); +// } +// +// private Map consistPlayerWinLossResults(int dealerScore, boolean isDealerBust) { +// Map playerWinLossResults = new LinkedHashMap<>(); +// List playingPlayers = players.getPlayers(); +// +// for (Player specificPlayer : playingPlayers) { +// Result specificPlayerResult = determinePlayerResult(dealerScore, isDealerBust, specificPlayer); +// playerWinLossResults.put(specificPlayer, specificPlayerResult); +// } +// +// return playerWinLossResults; +// } +// +// private Result determinePlayerResult(int dealerScore, boolean isDealerBust, Player specificPlayer) { +// return Result.determinePlayerResult( +// dealerScore, +// specificPlayer.calculateHandSum(), +// isDealerBust, +// specificPlayer.isBust() +// ); +// } +// +// private Map consistDealerResult(Map playerWinLossResults) { +// Map dealerWinLossResults = new HashMap<>(); +// List playingPlayers = playerWinLossResults.keySet().stream().toList(); +// for (Player player : playingPlayers) { +// Result dealerResult = playerWinLossResults.get(player).reverse(); +// int currentValue = dealerWinLossResults.getOrDefault(dealerResult, 0); +// dealerWinLossResults.put(dealerResult, currentValue + 1); +// } +// return dealerWinLossResults; +// } +} diff --git a/src/test/java/controller/BlackJackControllerTest.java b/src/test/java/controller/BlackJackControllerTest.java index cdb58586bdc..848cf65cca3 100644 --- a/src/test/java/controller/BlackJackControllerTest.java +++ b/src/test/java/controller/BlackJackControllerTest.java @@ -1,127 +1,119 @@ -package controller; - -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import domain.Card; -import domain.CardContents; -import domain.CardCreationStrategy; -import domain.CardShape; -import domain.Deck; -import domain.Player; -import dto.ParticipantDto; -import java.util.ArrayList; -import java.util.List; -import org.assertj.core.api.Assertions; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.InjectMocks; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; -import view.InputView; -import view.OutputView; - -@ExtendWith(MockitoExtension.class) -class BlackJackControllerTest { - private static final String TEST_NAME = "tester"; - - @Mock - private InputView inputView; - - @Mock - private OutputView outputView; - - @Mock - private CardCreationStrategy strategy; - - @InjectMocks - private BlackJackController controller; - - @Test - @DisplayName("이름 잘 물어봄") - void askPlayerNames_success() { - //given - when(inputView.readNames()).thenReturn(List.of("pobi", "gump")); - - //when - List result = controller.askPlayerNames(); - - //then - verify(outputView, times(1)).printNamePrompt(); - verify(inputView, times(1)).readNames(); - } - - @Test - @DisplayName("카드 내용 출력 전달 잘함") - void showPlayerCards_success() { - //given - List testCards = List.of( - new Card(CardShape.하트, CardContents.TWO), - new Card(CardShape.하트, CardContents.THREE), - new Card(CardShape.하트, CardContents.FOUR) - ); - CardCreationStrategy strategy = new CardCreationStrategy() { - @Override - public List create() { - return new ArrayList<>(testCards); - } - }; - - Deck totalDeck = Deck.createDeck(strategy); - Deck playerDeck = Deck.createParticipantDeck(totalDeck); - Player PLAYER = new Player(playerDeck, TEST_NAME); - ParticipantDto PARTICIPANT_DTO = ParticipantDto.from(PLAYER); - - //when && then - assertDoesNotThrow( - () -> controller.showPlayerCards(PARTICIPANT_DTO) - ); - } - - @Nested - class addDrawCardTest { - @Test - @DisplayName("질문해서 y 면 true 반환") - void askDrawCard_true() { - //given - when(inputView.readHitOrStand()).thenReturn("y"); - - //when - boolean result = controller.askDrawCard(TEST_NAME); - - //then - Assertions.assertThat(result).isTrue(); - } - - @Test - @DisplayName("질문해서 n이면 false 반환") - void askDrawCard_false() { - //given - when(inputView.readHitOrStand()).thenReturn("n"); - - //when - boolean result = controller.askDrawCard(TEST_NAME); - - //then - Assertions.assertThat(result).isFalse(); - } - } - - //private List createSampleCards() { -// CardShape[] shapes = CardShape.values(); -// CardContents[] contents = CardContents.values(); -// -// List sampleCards = new ArrayList<>(); -// for (CardShape cardShape : shapes) { -// for (CardContents content : contents) { -// sampleCards.add(new Card(cardShape, content)); +//package controller; +// +//import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +//import static org.mockito.Mockito.times; +//import static org.mockito.Mockito.verify; +//import static org.mockito.Mockito.when; +// +//import domain.Card; +//import domain.CardContents; +//import domain.CardCreationStrategy; +//import domain.CardShape; +//import domain.Deck; +//import domain.Player; +//import dto.ParticipantDto; +//import java.util.ArrayList; +//import java.util.List; +//import org.assertj.core.api.Assertions; +//import org.junit.jupiter.api.DisplayName; +//import org.junit.jupiter.api.Nested; +//import org.junit.jupiter.api.Test; +//import org.junit.jupiter.api.extension.ExtendWith; +//import org.mockito.InjectMocks; +//import org.mockito.Mock; +//import org.mockito.junit.jupiter.MockitoExtension; +//import view.InputView; +//import view.OutputView; +// +//@ExtendWith(MockitoExtension.class) +//class BlackJackControllerTest { +// private static final String TEST_NAME = "tester"; +// +// @Mock +// private InputView inputView; +// +// @Mock +// private OutputView outputView; +// +// @Mock +// private CardCreationStrategy strategy; +// +// @InjectMocks +// private BlackJackController controller; +// +// @Test +// @DisplayName("이름 잘 물어봄") +// void askPlayerNames_success() { +// //given +// when(inputView.readNames()).thenReturn(List.of("pobi", "gump")); +// +// //when +// List result = controller.askPlayerNames(); +// +// //then +// verify(outputView, times(1)).printNamePrompt(); +// verify(inputView, times(1)).readNames(); +// } +// +// @Test +// @DisplayName("카드 내용 출력 전달 잘함") +// void showPlayerCards_success() { +// //given +// List testCards = List.of( +// new Card(CardShape.하트, CardContents.TWO), +// new Card(CardShape.하트, CardContents.THREE), +// new Card(CardShape.하트, CardContents.FOUR) +// ); +// CardCreationStrategy strategy = new CardCreationStrategy() { +// @Override +// public List create() { +// return new ArrayList<>(testCards); // } +// }; +// +// Deck totalDeck = Deck.createDeck(strategy); +// Deck playerDeck = Deck.createParticipantDeck(totalDeck); +// Player PLAYER = new Player(playerDeck, TEST_NAME); +// ParticipantDto PARTICIPANT_DTO = ParticipantDto.from(PLAYER); +// +// //when && then +// assertDoesNotThrow( +// () -> controller.showPlayerCards(PARTICIPANT_DTO) +// ); +// } +// +// @Nested +// class addDrawCardTest { +// @Test +// @DisplayName("질문해서 y 면 true 반환") +// void askDrawCard_true() { +// //given +// when(inputView.readHitOrStand()).thenReturn("y"); +// +// //when +// boolean result = controller.askDrawCard(TEST_NAME); +// +// //then +// Assertions.assertThat(result).isTrue(); // } // -// return sampleCards; +// @Test +// @DisplayName("질문해서 n이면 false 반환") +// void askDrawCard_false() { +// //given +// when(inputView.readHitOrStand()).thenReturn("n"); +// +// //when +// boolean result = controller.askDrawCard(TEST_NAME); +// +// //then +// Assertions.assertThat(result).isFalse(); +// } // } -} +// +// //private List createSampleCards() { +/// / CardShape[] shapes = CardShape.values(); / CardContents[] contents = CardContents.values(); / / +/// List sampleCards = new ArrayList<>(); / for (CardShape cardShape : shapes) { / for +/// (CardContents content : contents) { / sampleCards.add(new Card(cardShape, content)); / } / +/// } / / return sampleCards; / } +//} From ea1ad519e883b4bcfa91e34b5dbd54b2b010ccb6 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 19:13:31 +0900 Subject: [PATCH 088/129] fix : rename card to cardsInfo --- src/main/java/dto/ParticipantDto.java | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/src/main/java/dto/ParticipantDto.java b/src/main/java/dto/ParticipantDto.java index e3a4a6fcaef..d06f08637f6 100644 --- a/src/main/java/dto/ParticipantDto.java +++ b/src/main/java/dto/ParticipantDto.java @@ -9,13 +9,12 @@ public record ParticipantDto(String name, List cards) { public static ParticipantDto consistWithInitialInfo(Participant participant) { String name = participant.getName(); - List cards = new ArrayList<>(); - for (Card card : participant.showInitialCard()) { - cards.add(CardDto.from(card)); - } + List cardsInfo = participant.showInitialCard() + .stream() + .map(CardDto::from) + .toList(); - int score = 0; - return new ParticipantDto(name, cards); + return new ParticipantDto(name, cardsInfo); } public static ParticipantDto from(Participant participant) { From 5397023c3b6770cccbd4ab72b8ea162a622bd5bf Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 21:06:17 +0900 Subject: [PATCH 089/129] fix : change return Type of hit or stand method MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 출력을 위해 command에 값을 반환 --- src/main/java/domain/BlackJackGame.java | 21 ++++++++++++-------- src/main/java/domain/Players.java | 26 +++++++++++++++---------- 2 files changed, 29 insertions(+), 18 deletions(-) diff --git a/src/main/java/domain/BlackJackGame.java b/src/main/java/domain/BlackJackGame.java index 76f372cc29d..b945241fad8 100644 --- a/src/main/java/domain/BlackJackGame.java +++ b/src/main/java/domain/BlackJackGame.java @@ -1,5 +1,6 @@ package domain; +import common.ErrorMessage; import dto.ParticipantDto; import java.util.List; import java.util.Optional; @@ -35,21 +36,25 @@ public Optional whoseTurn() { return players.findNotStayPlayer(); } - public void doHitProcess() { - players.findNotStayPlayer() - .ifPresent(player -> players.executeHit(player, totalDeck::drawCard)); + public ParticipantDto doHitProcess() { + Player newPlayer = players.findNotStayPlayer() + .map(player -> players.executeHit(player, totalDeck::drawCard)) + .orElseThrow(() -> new IllegalStateException(ErrorMessage.NO_MORE_PLAYABLE_PLAYER.getMessage())); + return ParticipantDto.from(newPlayer); } - public void doStandProcess() { - players.findNotStayPlayer() - .ifPresent(players::executeStand); + public ParticipantDto doStandProcess() { + Player newPlayer = players.findNotStayPlayer() + .map(players::executeStand) + .orElseThrow(() -> new IllegalStateException(ErrorMessage.NO_MORE_PLAYABLE_PLAYER.getMessage())); + return ParticipantDto.from(newPlayer); } - public List getPlayersGameSettingResults() { + public List getPlayersGameSettingStates() { return players.getInitialStates(); } - public ParticipantDto getDealerGameSettingResult() { + public ParticipantDto getDealerGameSettingState() { return ParticipantDto.consistWithInitialInfo(dealer); } // diff --git a/src/main/java/domain/Players.java b/src/main/java/domain/Players.java index b93b8ccd8de..5b09ec4b2fc 100644 --- a/src/main/java/domain/Players.java +++ b/src/main/java/domain/Players.java @@ -8,6 +8,7 @@ import java.util.Map; import java.util.Optional; import java.util.function.Supplier; +import java.util.function.UnaryOperator; public class Players { private static final int MAX_PLAYER_NUMBER = 5; @@ -57,18 +58,12 @@ public Optional findNotStayPlayer() { .findFirst(); } - public void executeHit(Player player, Supplier cardSupplier) { - int index = players.indexOf(player); - if (index != -1) { - players.set(index, player.hit(cardSupplier)); - } + public Player executeHit(Player player, Supplier cardSupplier) { + return applyAction(player, p -> p.hit(cardSupplier)); } - public void executeStand(Player player) { - int index = players.indexOf(player); - if (index != -1) { - players.set(index, player.stand()); - } + public Player executeStand(Player player) { + return applyAction(player, Player::stand); } public List getInitialStates() { @@ -76,6 +71,17 @@ public List getInitialStates() { .map(ParticipantDto::consistWithInitialInfo) .toList(); } + + private Player applyAction(Player player, UnaryOperator action) { + int index = players.indexOf(player); + if (index == -1) { + throw new IllegalArgumentException(ErrorMessage.PLAYER_NOT_FOUND.getMessage()); + } + + Player newPlayer = action.apply(player); + players.set(index, newPlayer); + return newPlayer; + } // public void hitPlayer(Player targetPlayer, Supplier cardSupplier) { // targetPlayer.hit(cardSupplier); // } From 0be109fab14668004a8fc2a9a044306687b0507d Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 21:07:01 +0900 Subject: [PATCH 090/129] feat : add new Error Message No more playable player --- src/main/java/common/ErrorMessage.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/common/ErrorMessage.java b/src/main/java/common/ErrorMessage.java index abe240b9bb0..a81f268e314 100644 --- a/src/main/java/common/ErrorMessage.java +++ b/src/main/java/common/ErrorMessage.java @@ -8,6 +8,7 @@ public enum ErrorMessage { ONLY_KO_AND_ENG("이름은 영어 또는 한국어만 가능합니다: "), NAME_UNIQUENESS_ERR("이름은 중복되면 안됩니다"), PLAYER_NOT_FOUND("해당 플레이어를 찾을 수 없습니다"), + NO_MORE_PLAYABLE_PLAYER("더 이상 게임을 진행할 수 있는 플레이어가 없습니다."), MAX_PLAYER_ERROR("최대 인원을 초과했습니다."); private final String message; From b7f7b6a46d28cf477afaf01f594ed9ea697b46b9 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 21:14:23 +0900 Subject: [PATCH 091/129] fix : rename file to GameResult MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit java의 Result와의 혼동을 막고자 GameResult로 네이밍 변경 --- src/main/java/domain/GameResult.java | 42 ++++++++++++++++++++++++++++ src/main/java/domain/Result.java | 42 ---------------------------- 2 files changed, 42 insertions(+), 42 deletions(-) create mode 100644 src/main/java/domain/GameResult.java delete mode 100644 src/main/java/domain/Result.java diff --git a/src/main/java/domain/GameResult.java b/src/main/java/domain/GameResult.java new file mode 100644 index 00000000000..b2d5ec4165b --- /dev/null +++ b/src/main/java/domain/GameResult.java @@ -0,0 +1,42 @@ +package domain; + +public enum GameResult { + 승, 무, 패; + + public static GameResult determinePlayerResult( + int dealerScore, + int playerScore, + boolean isDealerBust, + boolean isPlayerBust + ) { + if (isDealerBust) { + return GameResult.승; + } + if (isPlayerBust) { + return GameResult.패; + } + return compareScoreForCheckPlayerResult(dealerScore, playerScore); + } + + private static GameResult compareScoreForCheckPlayerResult(int dealerScore, int playerScore) { + if (dealerScore > playerScore) { + return GameResult.패; + } + if (dealerScore == playerScore) { + return GameResult.무; + } + return GameResult.승; + } + + public GameResult reverse() { + if (this.equals(GameResult.승)) { + return GameResult.패; + } + + if (this.equals(GameResult.패)) { + return GameResult.승; + } + + return GameResult.무; + } +} diff --git a/src/main/java/domain/Result.java b/src/main/java/domain/Result.java deleted file mode 100644 index 7a641ac8987..00000000000 --- a/src/main/java/domain/Result.java +++ /dev/null @@ -1,42 +0,0 @@ -package domain; - -public enum Result { - 승, 무, 패; - - public static Result determinePlayerResult( - int dealerScore, - int playerScore, - boolean isDealerBust, - boolean isPlayerBust - ) { - if (isDealerBust) { - return Result.승; - } - if (isPlayerBust) { - return Result.패; - } - return compareScoreForCheckPlayerResult(dealerScore, playerScore); - } - - private static Result compareScoreForCheckPlayerResult(int dealerScore, int playerScore) { - if (dealerScore > playerScore) { - return Result.패; - } - if (dealerScore == playerScore) { - return Result.무; - } - return Result.승; - } - - public Result reverse() { - if (this.equals(Result.승)) { - return Result.패; - } - - if (this.equals(Result.패)) { - return Result.승; - } - - return Result.무; - } -} From 24d433ce9c830278668a531a1592e3dc1e01d6d0 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 23:08:53 +0900 Subject: [PATCH 092/129] fix : delete unnecessary method --- src/main/java/domain/Players.java | 17 ----------------- 1 file changed, 17 deletions(-) diff --git a/src/main/java/domain/Players.java b/src/main/java/domain/Players.java index 5b09ec4b2fc..33f62ef4e77 100644 --- a/src/main/java/domain/Players.java +++ b/src/main/java/domain/Players.java @@ -3,9 +3,7 @@ import common.ErrorMessage; import dto.ParticipantDto; import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; import java.util.Optional; import java.util.function.Supplier; import java.util.function.UnaryOperator; @@ -82,19 +80,4 @@ private Player applyAction(Player player, UnaryOperator action) { players.set(index, newPlayer); return newPlayer; } -// public void hitPlayer(Player targetPlayer, Supplier cardSupplier) { -// targetPlayer.hit(cardSupplier); -// } - -// public List getPlayers() { -// return players; -// } - - public Map> getDecksPerPlayer() { - Map> decksPerUser = new LinkedHashMap<>(); - for (Player player : players) { - decksPerUser.put(player.getName(), player.showOwnCards()); - } - return decksPerUser; - } } From 5fc23ff73e67a484f583df45fdfcedb41671d160 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 23:09:24 +0900 Subject: [PATCH 093/129] fix : change method acceess modifier --- src/main/java/domain/Hand.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/domain/Hand.java b/src/main/java/domain/Hand.java index 7df4fea4daa..ba38d2be790 100644 --- a/src/main/java/domain/Hand.java +++ b/src/main/java/domain/Hand.java @@ -34,7 +34,7 @@ public Hand addCard(Card card) { return new Hand(mutableCards); } - private int calculateCardScoreSum() { + public int calculateCardScoreSum() { int sumExceptAce = calculateCardScoreSumExceptAce(); int sumAce = AceScoreDiscriminator.calculateAceCardsSum(cards, sumExceptAce); From 753f32ef7a517197004eaa04367ee6f5fd57281a Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 23:10:29 +0900 Subject: [PATCH 094/129] fix : comment out dealer hit or stand logic --- .../java/controller/BlackJackController.java | 63 ++++++++++++------- src/main/java/domain/BlackJackGame.java | 24 ++++--- src/main/java/domain/Dealer.java | 22 ++++--- src/test/java/domain/DealerTest.java | 4 +- 4 files changed, 73 insertions(+), 40 deletions(-) diff --git a/src/main/java/controller/BlackJackController.java b/src/main/java/controller/BlackJackController.java index e7ca3631136..404467f4c45 100644 --- a/src/main/java/controller/BlackJackController.java +++ b/src/main/java/controller/BlackJackController.java @@ -3,8 +3,8 @@ import domain.BlackJackGame; import domain.CardCreationStrategy; import domain.Player; +import dto.ParticipantDto; import java.util.List; -import java.util.Optional; import java.util.function.Supplier; import view.InputView; import view.OutputView; @@ -24,29 +24,12 @@ public BlackJackController(InputView inputView, OutputView outputView, CardCreat public void doGameProcess() { BlackJackGame game = retry(this::readyGame); outputView.printInitialStates( - game.getDealerGameSettingResult(), - game.getPlayersGameSettingResults() + game.getDealerGameSettingState(), + game.getPlayersGameSettingStates() ); - //2. 게임을 진행하라 - while (true) { - Optional currentPlayer = game.whoseTurn(); - if (currentPlayer.isEmpty()) { - break; - } - - String currentUserName = currentPlayer.get().getName(); - outputView.printHitOrStandPrompt(currentUserName); - String hitOrStand = inputView.readHitOrStand(); - if (hitOrStand.equals("y")) { - game.doHitProcess(); //TODO: 출력이 야기되어야 합니다. - } else { - game.doStandProcess(); - } - } -// -// //3. 게임 결과를 구하라 -// blackJackGame.end(this); + playPlayersTurn(game); + playDealerTurn(game); } private BlackJackGame readyGame() { @@ -55,6 +38,42 @@ private BlackJackGame readyGame() { return BlackJackGame.ready(playerNames, strategy); } + private void playDealerTurn(BlackJackGame game) { + while (game.doDealerHitOrStandProcess()) { + outputView.printDealerAddCardNotice(); + } + } + + private void playPlayersTurn(BlackJackGame game) { + while (game.whoseTurn().isPresent()) { + Player currentPlayer = game.whoseTurn().get(); + + outputView.printHitOrStandPrompt(currentPlayer.getName()); + String hitOrStandInfo = retry(inputView::readHitOrStand); + doHitOrStand(hitOrStandInfo, game); + } + } + + private void doHitOrStand(String hitOrStand, BlackJackGame game) { + if (hitOrStand.equals("y")) { + handlePlayerHitProcess(game); + return; + } + handlePlayerStandProcess(game); + } + + private void handlePlayerStandProcess(BlackJackGame game) { + ParticipantDto playerState = game.doStandProcess(); + if (playerState.cards().size() == 2) { + outputView.printUserState(playerState); + } + } + + private void handlePlayerHitProcess(BlackJackGame game) { + ParticipantDto playerState = game.doHitProcess(); + outputView.printUserState(playerState); + } + private T retry(Supplier supplier) { while (true) { try { diff --git a/src/main/java/domain/BlackJackGame.java b/src/main/java/domain/BlackJackGame.java index b945241fad8..3e23470f10e 100644 --- a/src/main/java/domain/BlackJackGame.java +++ b/src/main/java/domain/BlackJackGame.java @@ -7,8 +7,8 @@ public class BlackJackGame { private final Deck totalDeck; - private final Dealer dealer; private final Players players; + private Dealer dealer; private BlackJackGame(Deck totalDeck, Dealer dealer, Players players) { this.totalDeck = totalDeck; @@ -18,18 +18,18 @@ private BlackJackGame(Deck totalDeck, Dealer dealer, Players players) { public static BlackJackGame ready(List playerNames, CardCreationStrategy strategy) { Deck totalDeck = Deck.createDeck(strategy); - - Dealer dealer = createNewDealer(totalDeck); - Players players = Players.of(playerNames, totalDeck); - - return new BlackJackGame(totalDeck, dealer, players); + return new BlackJackGame( + totalDeck, + createNewDealer(totalDeck), + Players.of(playerNames, totalDeck) + ); } private static Dealer createNewDealer(Deck totalDeck) { List dealersInitialCards = totalDeck.drawTwoCards(); Card card1 = dealersInitialCards.get(0); Card card2 = dealersInitialCards.get(1); - return new Dealer(Hand.of(card1, card2)); + return Dealer.from(Hand.of(card1, card2)); } public Optional whoseTurn() { @@ -50,6 +50,12 @@ public ParticipantDto doStandProcess() { return ParticipantDto.from(newPlayer); } + public boolean doDealerHitOrStandProcess() { + Optional nextDealer = dealer.addCard(totalDeck::drawCard); + nextDealer.ifPresent(this::updateDealer); + return nextDealer.isPresent(); + } + public List getPlayersGameSettingStates() { return players.getInitialStates(); } @@ -57,6 +63,10 @@ public List getPlayersGameSettingStates() { public ParticipantDto getDealerGameSettingState() { return ParticipantDto.consistWithInitialInfo(dealer); } + + private void updateDealer(Dealer newDealer) { + this.dealer = newDealer; + } // // public void play(GameDelegate observer) { // List individualPlayers = players.getPlayers(); diff --git a/src/main/java/domain/Dealer.java b/src/main/java/domain/Dealer.java index 3407ae3ca2f..3c7e1cdeac3 100644 --- a/src/main/java/domain/Dealer.java +++ b/src/main/java/domain/Dealer.java @@ -1,26 +1,32 @@ package domain; import java.util.List; +import java.util.Optional; +import java.util.function.Supplier; public class Dealer extends Participant { private static final int MINIMUM_TOTAL_SCORE = 16; private static final String DEALER_NAME = "딜러"; - public Dealer(Hand hand) { + private Dealer(Hand hand) { super(DEALER_NAME, hand); } + public static Dealer from(Hand hand) { + return new Dealer(hand); + } + @Override public List showInitialCard() { List ownCards = super.showOwnCards(); return List.of(ownCards.getFirst()); } -// @Override -// public Optional addCard(Deck totalDeck) { -// if (super.calculateHandSum() <= MINIMUM_TOTAL_SCORE) { -// return super.addCard(totalDeck); -// } -// return Optional.empty(); -// } + public Optional addCard(Supplier cardSupplier) { + if (hand.calculateCardScoreSum() <= MINIMUM_TOTAL_SCORE) { + Hand newHand = this.hand.addCard(cardSupplier.get()); + return Optional.of(new Dealer(newHand)); + } + return Optional.empty(); + } } diff --git a/src/test/java/domain/DealerTest.java b/src/test/java/domain/DealerTest.java index c34cf078e74..4f0bf656042 100644 --- a/src/test/java/domain/DealerTest.java +++ b/src/test/java/domain/DealerTest.java @@ -14,9 +14,7 @@ void dealer_create_success() { new Card(CardShape.클로버, CardContents.FIVE) ); - assertDoesNotThrow( - () -> new Dealer(dealerHand) - ); + assertDoesNotThrow(() -> Dealer.from(dealerHand)); } // @Test From a9acadf92e202dbeadbce8955a9d41c44284b99d Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 23:11:29 +0900 Subject: [PATCH 095/129] fix : add common interface and comment out getOwnCardsSum method --- src/main/java/domain/Participant.java | 26 +++++++++----------------- 1 file changed, 9 insertions(+), 17 deletions(-) diff --git a/src/main/java/domain/Participant.java b/src/main/java/domain/Participant.java index c38391813b6..f77b8183962 100644 --- a/src/main/java/domain/Participant.java +++ b/src/main/java/domain/Participant.java @@ -32,6 +32,8 @@ private static void validateIsNotBlank(String name) { } } + public abstract List showInitialCard(); + public String getName() { return name; } @@ -40,21 +42,11 @@ public List showOwnCards() { return hand.showCards(); } - public abstract List showInitialCard(); -// -//// public abstract List getInitialVisibleCards(); -// -// public boolean isBust() { -// return hand.isBust(); -// } -// -// public int calculateHandSum() { -// return hand.calculateCardScoreSum(); -// } -// -// public Optional addCard(Deck totalDeck) { -// Card newCard = totalDeck.drawCard(); -// this.hand.addCard(newCard); -// return Optional.of(newCard); -// } + public int getOwnCardsSum() { + return this.hand.calculateCardScoreSum(); + } + + public boolean isBust() { + return hand.isBust(); + } } \ No newline at end of file From 05b0758e7c540806d2b31148fd0f06d562a18df1 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 23:11:57 +0900 Subject: [PATCH 096/129] fix : comment out printing logic that need to run main playing logic --- src/main/java/view/OutputView.java | 45 ++++++++++++++++++------------ 1 file changed, 27 insertions(+), 18 deletions(-) diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 4592aa33d8f..47030b3bcfa 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -10,9 +10,9 @@ public class OutputView { private static final String NAME_PROMPT = "게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)"; private static final String INITIAL_CARD_SHARE = "딜러와 %s에게 2장을 나누었습니다.\n"; private static final String HIT_OR_STAND_PROMPT = "%s는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)\n"; - private static final String ADDITIONAL_CARD_FOR_DEALER_DESCRIPTION = "딜러는 16이하라 한장의 카드를 더 받았습니다."; - private static final String PARTICIPANT_CARD_INFO_FORMAT = "%s카드: %s"; private static final String PARTICIPANT_CARD_INFO_FORMAT_LINE = "%s카드: %s\n"; + private static final String DEALER_ADD_CARD_NOTICE = "\n딜러는 16이하라 한장의 카드를 더 받았습니다.\n"; + private static final String PARTICIPANT_CARD_INFO_FORMAT = "%s카드: %s"; private static final String PARTICIPANT_CARD_INFO_WITH_SUM_FORMAT = "%s - 결과: %d\n"; private static final String WIN_LOSS_RESULT_HEADER = "## 최종 승패\n"; private static final String WIN_LOSS_RESULT_FORMAT = "%s: %s\n"; @@ -47,17 +47,16 @@ public void printHitOrStandPrompt(String name) { System.out.printf(HIT_OR_STAND_PROMPT, name); } + public void printUserState(ParticipantDto participantDto) { + String userCardInfo = consistCardInfo(participantDto); + System.out.println(userCardInfo); + } + + public void printDealerAddCardNotice() { + System.out.println(DEALER_ADD_CARD_NOTICE); + } + // -// public void printUserCardInfo(ParticipantDto participantDto) { -// String userCardInfo = consistCardInfo(PARTICIPANT_CARD_INFO_FORMAT_LINE, participantDto); -// System.out.println(userCardInfo); -// } -// -// public void printAdditionalCardForDealerDescription() { -// System.out.println(); -// System.out.println(ADDITIONAL_CARD_FOR_DEALER_DESCRIPTION); -// } -// // public void printCardInfoWithSum(GameResultDto gameResultDto) { // StringBuilder cardInfoWithSum = new StringBuilder(); // @@ -88,12 +87,22 @@ public void printHitOrStandPrompt(String name) { // } // private String consistCardInfo(ParticipantDto participantDto) { - List cardInfos = new ArrayList<>(); - for (CardDto card : participantDto.cards()) { - cardInfos.add(card.cardContentNumber() + card.cardShape()); - } - return String.format(OutputView.PARTICIPANT_CARD_INFO_FORMAT_LINE, participantDto.name(), - String.join(DELIMITER, cardInfos)); + List ownCards = participantDto.cards(); + List cardsInformation = extractCardsInfo(ownCards); + + String cardsInformationText = String.join(DELIMITER, cardsInformation); + + return String.format( + PARTICIPANT_CARD_INFO_FORMAT_LINE, + participantDto.name(), + cardsInformationText + ); + } + + private List extractCardsInfo(List cards) { + return cards.stream() + .map(card -> card.cardContentNumber() + card.cardShape()) + .toList(); } // // private String consistCardInfoWithSum(ParticipantDto participantDto) { From 960e9c0395a8c4bfb36c33954be4886590930614 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Wed, 11 Mar 2026 23:13:41 +0900 Subject: [PATCH 097/129] fix : upgrade logic aspect of visibiliity --- src/main/java/dto/ParticipantDto.java | 27 ++++++++++++++++----------- 1 file changed, 16 insertions(+), 11 deletions(-) diff --git a/src/main/java/dto/ParticipantDto.java b/src/main/java/dto/ParticipantDto.java index d06f08637f6..88c90c6309c 100644 --- a/src/main/java/dto/ParticipantDto.java +++ b/src/main/java/dto/ParticipantDto.java @@ -8,24 +8,29 @@ public record ParticipantDto(String name, List cards) { public static ParticipantDto consistWithInitialInfo(Participant participant) { String name = participant.getName(); + List initialCards = participant.showInitialCard(); - List cardsInfo = participant.showInitialCard() - .stream() - .map(CardDto::from) - .toList(); - - return new ParticipantDto(name, cardsInfo); + return new ParticipantDto( + name, + consistCardsInfo(initialCards) + ); } public static ParticipantDto from(Participant participant) { String name = participant.getName(); + List ownCards = participant.showOwnCards(); - List cards = new ArrayList<>(); - for (Card card : participant.showOwnCards()) { - cards.add(CardDto.from(card)); - } + return new ParticipantDto( + name, + consistCardsInfo(ownCards) + ); + } - return new ParticipantDto(name, cards); + private static List consistCardsInfo(List initialCards) { + return initialCards + .stream() + .map(CardDto::from) + .toList(); } public static List listOf(List participants) { From 1c204252097647231895b03bc8c9075dd9952246 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Thu, 12 Mar 2026 11:14:54 +0900 Subject: [PATCH 098/129] fix : add ommited feature and test logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존에 누락된 blackJack 로직 추가 - 수정 과정에서 누락시킨 테스트 재생성 - 기존에 존재하지 않았던 case 에 대한 테스트 코드 추가 --- src/main/java/domain/Hand.java | 4 + src/main/java/domain/Participant.java | 4 + src/test/java/domain/HandTest.java | 141 +++++++++++++++++++++++--- 3 files changed, 136 insertions(+), 13 deletions(-) diff --git a/src/main/java/domain/Hand.java b/src/main/java/domain/Hand.java index ba38d2be790..161a396d30b 100644 --- a/src/main/java/domain/Hand.java +++ b/src/main/java/domain/Hand.java @@ -24,6 +24,10 @@ public boolean isFull() { return calculateCardScoreSum() == BUST_CRITERIA; } + public boolean isBlackJack() { + return isFull() && cards.size() == 2; + } + public List showCards() { return Collections.unmodifiableList(cards); } diff --git a/src/main/java/domain/Participant.java b/src/main/java/domain/Participant.java index f77b8183962..68363572eaf 100644 --- a/src/main/java/domain/Participant.java +++ b/src/main/java/domain/Participant.java @@ -49,4 +49,8 @@ public int getOwnCardsSum() { public boolean isBust() { return hand.isBust(); } + + public boolean isBlackJack() { + return hand.isBlackJack(); + } } \ No newline at end of file diff --git a/src/test/java/domain/HandTest.java b/src/test/java/domain/HandTest.java index e2e8c217683..b7db535713a 100644 --- a/src/test/java/domain/HandTest.java +++ b/src/test/java/domain/HandTest.java @@ -2,22 +2,38 @@ import java.util.List; import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; class HandTest { + Hand testHandThatValueFive; + + @BeforeEach + void setUpHand() { + testHandThatValueFive = Hand.of( + new Card(CardShape.다이아몬드, CardContents.TWO), + new Card(CardShape.다이아몬드, CardContents.THREE) + ); + } + @Nested class isBustTest { @Test - @DisplayName("카드 합이 21 이하이면 isBust는 False다") + @DisplayName("카드 합이 21이하면 isBust는 False다") void isBust_False() { - Hand testHandThatValueFive = Hand.of( - new Card(CardShape.다이아몬드, CardContents.TWO), - new Card(CardShape.다이아몬드, CardContents.THREE) - ); + //when + boolean result = testHandThatValueFive.isBust(); + //then + Assertions.assertFalse(result); + } + + @Test + @DisplayName("카드 합이 21 이어도 isBust는 False다") + void isBust_False_TwentyOne() { List cardsThatValue16 = List.of( new Card(CardShape.다이아몬드, CardContents.SIX), new Card(CardShape.다이아몬드, CardContents.TEN) @@ -37,10 +53,6 @@ void isBust_False() { @Test @DisplayName("카드 합이 21 초과이면 isBust는 True다") void isBust_True() { - Hand testHandThatValueFive = Hand.of( - new Card(CardShape.다이아몬드, CardContents.TWO), - new Card(CardShape.다이아몬드, CardContents.THREE) - ); List cardsThatValue17 = List.of( new Card(CardShape.다이아몬드, CardContents.SEVEN), new Card(CardShape.다이아몬드, CardContents.TEN) @@ -58,14 +70,106 @@ void isBust_True() { } } + @Nested + class isFullTest { + @Test + @DisplayName("21이면 isFull 은 True다") + void isTrue_true() { + List cardsThatValue16 = List.of( + new Card(CardShape.다이아몬드, CardContents.SIX), + new Card(CardShape.다이아몬드, CardContents.TEN) + ); + + for (Card card : cardsThatValue16) { + testHandThatValueFive = testHandThatValueFive.addCard(card); + } + + //when + boolean result = testHandThatValueFive.isFull(); + + //then + Assertions.assertTrue(result); + } + + @Test + @DisplayName("21이 아니면 isFull 은 False다") + void isTrue_false() { + //when + boolean result = testHandThatValueFive.isFull(); + + //then + Assertions.assertFalse(result); + } + } + + @Nested + class isBlackJackTest { + @Test + @DisplayName("카드 합이 21이 아니면 BlackJack 절대 아니다") + void isBlackJack_false() { + List cardsThatValue16 = List.of( + new Card(CardShape.다이아몬드, CardContents.SIX), + new Card(CardShape.다이아몬드, CardContents.TEN) + ); + + for (Card card : cardsThatValue16) { + testHandThatValueFive = testHandThatValueFive.addCard(card); + } + + //when + boolean result = testHandThatValueFive.isBlackJack(); + + //then + Assertions.assertFalse(result); + } + + @Test + @DisplayName("카드 합이 21이어도 카드 개수가 2장이 아니면 BlackJack 절대 아니다") + void isBlackJack_false_cardSize_not_two() { + //when + boolean result = testHandThatValueFive.isBlackJack(); + + //then + Assertions.assertFalse(result); + } + + @Test + @DisplayName("카드 합이 21이고 카드 개수가 2장이면 BlackJack 이다") + void isBlackJack_true() { + //given + Hand blackJackHand = Hand.of( + new Card(CardShape.다이아몬드, CardContents.TEN), + new Card(CardShape.다이아몬드, CardContents.A) + ); + //when + boolean result = blackJackHand.isBlackJack(); + + //then + Assertions.assertTrue(result); + } + } + + @Test + @DisplayName("카드를 잘 반환한다") + void showCards_test() { + //given + int expectSize = testHandThatValueFive.showCards().size(); + Card diamondTwo = new Card(CardShape.다이아몬드, CardContents.TWO); + Card diamondThree = new Card(CardShape.다이아몬드, CardContents.THREE); + + //when + List result = testHandThatValueFive.showCards(); + + //then + Assertions.assertEquals(expectSize, result.size()); + Assertions.assertTrue(result.contains(diamondTwo)); + Assertions.assertTrue(result.contains(diamondThree)); + } + @Test @DisplayName("받은 카드 1장을 잘 추가한다") void addCard_success() { //given - Hand testHandThatValueFive = Hand.of( - new Card(CardShape.다이아몬드, CardContents.TWO), - new Card(CardShape.다이아몬드, CardContents.THREE) - ); Card testCard = new Card(CardShape.하트, CardContents.SEVEN); int beforeAddCardSize = testHandThatValueFive.showCards().size(); @@ -76,4 +180,15 @@ void addCard_success() { int afterAddCardSize = testHandThatValueFive.showCards().size(); Assertions.assertEquals(beforeAddCardSize + 1, afterAddCardSize); } + + @Test + @DisplayName("카드의 합을 잘 구한다") + void calculateCardScoreSum_success() { + //when + int result = testHandThatValueFive.calculateCardScoreSum(); + int expect = 5; + + //then + Assertions.assertEquals(expect, result); + } } \ No newline at end of file From 984032e72d0026f16774f61a25ef1c1d5cc8440d Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Thu, 12 Mar 2026 11:35:16 +0900 Subject: [PATCH 099/129] feat : add compare transfer feature --- src/main/java/domain/Player.java | 12 ++- src/test/java/domain/PlayerTest.java | 128 +++++++++++---------------- 2 files changed, 58 insertions(+), 82 deletions(-) diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java index 66297244318..8b1ef3f5de3 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/Player.java @@ -16,10 +16,6 @@ public static Player from(String name, Hand hand) { return new Player(name, hand, false); } - public Player stand() { - return new Player(this.name, this.hand, true); - } - public Player hit(Supplier cardSupplier) { if (isFinished()) { return this; @@ -29,10 +25,18 @@ public Player hit(Supplier cardSupplier) { return new Player(this.name, newHand, false); } + public Player stand() { + return new Player(this.name, this.hand, true); + } + public boolean isFinished() { return hand.isBust() || hand.isFull() || isStay; } + public GameResult compare(Dealer dealer) { + return GameResult.decidePlayerResult(dealer, this); + } + @Override public List showInitialCard() { return super.showOwnCards(); diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java index 4f44d816917..e5026f42919 100644 --- a/src/test/java/domain/PlayerTest.java +++ b/src/test/java/domain/PlayerTest.java @@ -2,7 +2,6 @@ import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertFalse; import java.util.LinkedList; import java.util.List; @@ -36,8 +35,15 @@ class hitTest { new Card(CardShape.스페이드, CardContents.J), new Card(CardShape.클로버, CardContents.FIVE) ); + + Queue onlyTwoTenCards = new LinkedList<>(List.of( + new Card(CardShape.하트, CardContents.TEN), + new Card(CardShape.하트, CardContents.J) + )); + Supplier onlyTwoTenCardSupplier = onlyTwoTenCards::poll; + String testName = "gump"; - Player testPlayer = Player.from(testName, playerHand); + Player testPlayerWhoHoldTotal15Cards = Player.from(testName, playerHand); @Test @DisplayName("hit 할 수 있는 상태이면 hit를 진행한다") @@ -49,28 +55,42 @@ void hit_do() { Supplier testCardSupplier = () -> testDeck.poll(); //when - testPlayer.hit(testCardSupplier); + testPlayerWhoHoldTotal15Cards = testPlayerWhoHoldTotal15Cards.hit(testCardSupplier); //then Assertions.assertThat(testCardSupplier.get()).isNull(); } @Test - @DisplayName("hit 할 수 없는 상태이면 hit를 진행하지 않는다") + @DisplayName("hit 할 수 없는 상태이면 카드 뽑기를 내부적으로 진행하지 않는다") void hit_do_not() { //given - Queue testDeck = new LinkedList<>(List.of( - new Card(CardShape.하트, CardContents.TEN), + testPlayerWhoHoldTotal15Cards = testPlayerWhoHoldTotal15Cards.hit(onlyTwoTenCardSupplier); + + //when + testPlayerWhoHoldTotal15Cards = testPlayerWhoHoldTotal15Cards.hit(onlyTwoTenCardSupplier); + + //then + Assertions.assertThat(onlyTwoTenCardSupplier.get()).isNotNull(); + } + + @Test + @DisplayName("hit를 요구했지만 isStay가 false이지만 bust 상태여도 변화가 없다") + void hit_butBust_and_finish() { + //given + Hand playerHand = Hand.of( + new Card(CardShape.스페이드, CardContents.TEN), new Card(CardShape.클로버, CardContents.TEN) - )); - Supplier testCardSupplier = () -> testDeck.poll(); - testPlayer.hit(testCardSupplier); + ); + String testName = "gump"; + Player testPlayer = Player.from(testName, playerHand); + testPlayer.hit(onlyTwoTenCardSupplier); //when - testPlayer.hit(testCardSupplier); + Player hitResultPlayer = testPlayer.hit(onlyTwoTenCards::poll); //then - Assertions.assertThat(testCardSupplier.get()).isNotNull(); + assertEquals(testPlayer.hashCode(), hitResultPlayer.hashCode()); } } @@ -92,51 +112,27 @@ void stand_and_finish() { Assertions.assertThat(testPlayer.isFinished()).isTrue(); } - @Nested - class HitClass { - Queue onlyTenCardsDeck = new LinkedList<>(List.of( - new Card(CardShape.하트, CardContents.TEN), - new Card(CardShape.하트, CardContents.J), - new Card(CardShape.하트, CardContents.K), - new Card(CardShape.하트, CardContents.Q) - )); - - @Test - @DisplayName("hit를 잘 수행한다") - void hit_and_not_finished() { - //given - Hand playerHand = Hand.of( - new Card(CardShape.스페이드, CardContents.A), - new Card(CardShape.클로버, CardContents.TWO) - ); - String testName = "gump"; - Player testPlayer = Player.from(testName, playerHand); - - //when - testPlayer = testPlayer.hit(onlyTenCardsDeck::poll); - - //then - assertFalse(testPlayer.isFinished()); - } - - @Test - @DisplayName("hit를 요구했지만 bust 상태이면 변화가 없다") - void hit_butBust_and_finish() { - //given - Hand playerHand = Hand.of( - new Card(CardShape.스페이드, CardContents.TEN), - new Card(CardShape.클로버, CardContents.TEN) - ); - String testName = "gump"; - Player testPlayer = Player.from(testName, playerHand); - testPlayer.hit(onlyTenCardsDeck::poll); + @Test + @DisplayName("compare는 게임 결과 객체를 잘 반환한다") + void lose_when_dealer_blackjack() { + //given + String testPlayerName = "rati"; + Hand blackJackHand = Hand.of( + new Card(CardShape.스페이드, CardContents.J), + new Card(CardShape.클로버, CardContents.A) + ); + Hand notBlackJackAndNotBustHand = Hand.of( + new Card(CardShape.스페이드, CardContents.TWO), + new Card(CardShape.클로버, CardContents.THREE) + ); + Dealer testDealer = Dealer.from(blackJackHand); + Player testPlayer = Player.from(testPlayerName, notBlackJackAndNotBustHand); - //when - Player hitResultPlayer = testPlayer.hit(onlyTenCardsDeck::poll); + //when + GameResult result = testPlayer.compare(testDealer); - //then - assertEquals(testPlayer.hashCode(), hitResultPlayer.hashCode()); - } + //then + assertEquals(GameResult.class, result.getClass()); } @Test @@ -157,28 +153,4 @@ void equal_when_name_equal() { //when, then Assertions.assertThat(testPlayer1.equals(testPlayer2)).isTrue(); } -// @Test -// @DisplayName("플레이어가 카드를 한 장 더 받는다") -// void addCardWhenSumBelowMinimum() { -// //given -// Card expectResultCard = new Card(CardShape.스페이드, CardContents.A); -// CardCreationStrategy playerCardCreationStrategy = () -> { -// Card spadeJ = new Card(CardShape.스페이드, CardContents.J); -// return new ArrayList<>(List.of(spadeJ)); -// }; -// CardCreationStrategy totalCardCreationStrategy = () -> { -// Card heartA = new Card(CardShape.하트, CardContents.TWO); -// return new ArrayList<>(List.of(expectResultCard, heartA)); -// }; -// Deck playerDeck = Deck.createDeck(playerCardCreationStrategy); -// String testPlayerName = "pobi"; -// Deck totalDeck = Deck.createDeck(totalCardCreationStrategy); -// Player player = new Player(playerDeck, testPlayerName); -// -// //when -// Card resultCard = player.addCard(totalDeck).get(); -// -// //then -// Assertions.assertThat(resultCard).isEqualTo(expectResultCard); -// } } \ No newline at end of file From 0721dee7e5942148678306fc498063b84f8657d3 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Thu, 12 Mar 2026 12:12:21 +0900 Subject: [PATCH 100/129] fix : rename file and add collecting feature of PlayerResultDto MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - players 사용 시 player와 구분이 어려워 multiplayers로 변경 - 출력 시 사용될 정보를 각 정보 전문가에게서 가져오는 로직 추가 - 추가된 기능 관련 테스트 추가 - 기존 기능 중 누락된 테스트 케이스 테스트 추가 --- .../{Players.java => MultiPlayers.java} | 15 +- src/test/java/domain/MultiPlayersTest.java | 202 ++++++++++++++++++ src/test/java/domain/PlayersTest.java | 120 ----------- 3 files changed, 213 insertions(+), 124 deletions(-) rename src/main/java/domain/{Players.java => MultiPlayers.java} (85%) create mode 100644 src/test/java/domain/MultiPlayersTest.java delete mode 100644 src/test/java/domain/PlayersTest.java diff --git a/src/main/java/domain/Players.java b/src/main/java/domain/MultiPlayers.java similarity index 85% rename from src/main/java/domain/Players.java rename to src/main/java/domain/MultiPlayers.java index 33f62ef4e77..e10719ed26b 100644 --- a/src/main/java/domain/Players.java +++ b/src/main/java/domain/MultiPlayers.java @@ -2,23 +2,24 @@ import common.ErrorMessage; import dto.ParticipantDto; +import dto.PlayerResultDto; import java.util.ArrayList; import java.util.List; import java.util.Optional; import java.util.function.Supplier; import java.util.function.UnaryOperator; -public class Players { +public class MultiPlayers { private static final int MAX_PLAYER_NUMBER = 5; private final List players; - private Players(List players) { + private MultiPlayers(List players) { this.players = players; } - public static Players of(List playerNames, Deck totalDeck) { - return new Players( + public static MultiPlayers of(List playerNames, Deck totalDeck) { + return new MultiPlayers( new ArrayList<>(createPlayers(playerNames, totalDeck)) ); } @@ -70,6 +71,12 @@ public List getInitialStates() { .toList(); } + public List checkPlayersGameResult(Dealer dealer) { + return players.stream().map( + player -> PlayerResultDto.from(player, dealer) + ).toList(); + } + private Player applyAction(Player player, UnaryOperator action) { int index = players.indexOf(player); if (index == -1) { diff --git a/src/test/java/domain/MultiPlayersTest.java b/src/test/java/domain/MultiPlayersTest.java new file mode 100644 index 00000000000..5c9fd116f7d --- /dev/null +++ b/src/test/java/domain/MultiPlayersTest.java @@ -0,0 +1,202 @@ +package domain; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import common.ErrorMessage; +import dto.ParticipantDto; +import dto.PlayerResultDto; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.Queue; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class MultiPlayersTest { + private static final List TEST_PLAYER_NAMES = List.of("pobi", "terry", "rati", "gump"); + + Deck totalDeck; + + @BeforeEach + void setUpTotalDeck() { + CardCreationStrategy totalCardCreationStrategy = this::createSampleCards; + totalDeck = Deck.createDeck(totalCardCreationStrategy); + } + + @Nested + class ofTest { + @Test + @DisplayName("생성 잘 한다") + void of_good() { + //when, then + assertDoesNotThrow( + () -> MultiPlayers.of(TEST_PLAYER_NAMES, totalDeck) + ); + } + + @Test + @DisplayName("이름이 중복되면 오류가 발생한다") + void of_fail_duplication() { + //given + List testPlayerNames = List.of("pobi", "pobi"); + + //when && then + assertThatThrownBy(() -> MultiPlayers.of(testPlayerNames, totalDeck)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ErrorMessage.NAME_UNIQUENESS_ERR.getMessage()); + } + + @Test + @DisplayName("5명 초과 시 오류가 발생한다") + void of_fail_too_many_players() { + //given + List testPlayerNames = List.of("pobi", "gump", "rati", "terry", "neo", "james"); + + //when && then + assertThatThrownBy(() -> MultiPlayers.of(testPlayerNames, totalDeck)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ErrorMessage.MAX_PLAYER_ERROR.getMessage()); + } + } + + @Nested + class findNotStayPlayerTest { + @Test + @DisplayName("stay가 아닌 사용자가 있으면 Player를 반환한다") + void findNotStayPlayer_exist() { + //given + MultiPlayers testMultiPlayers = MultiPlayers.of(TEST_PLAYER_NAMES, totalDeck); + + //when + Optional result = testMultiPlayers.findNotStayPlayer(); + + //then + assertNotNull(result.get()); + } + + @Test + @DisplayName("stay가 아닌 사용자가 없으면 빈 Optional를 반환한다") + void findNotStayPlayer_not_exist() { + //given + MultiPlayers testMultiPlayers = MultiPlayers.of(TEST_PLAYER_NAMES, totalDeck); + while (true) { + Optional findResult = testMultiPlayers.findNotStayPlayer(); + if (findResult.isEmpty()) { + break; + } + testMultiPlayers.executeStand(findResult.get()); + } + + //when + Optional result = testMultiPlayers.findNotStayPlayer(); + + //then + assertTrue(result.isEmpty()); + } + } + + @Nested + class executeToPlayer { + @Test + @DisplayName("hit가 정상적으로 다른 플레이이에게 요청이 되면 결과로 Player 객체를 받는다") + void executeHit_good() { + //given + MultiPlayers testMultiPlayers = MultiPlayers.of(TEST_PLAYER_NAMES, totalDeck); + Player testPlayer = testMultiPlayers.findNotStayPlayer().get(); + + //when + Player result = testMultiPlayers.executeHit(testPlayer, totalDeck::drawCard); + + //then + assertEquals(Player.class, result.getClass()); + } + + @Test + @DisplayName("stand가 정상적으로 다른 플레이이에게 요청이 되면 결과로 Player 객체를 받는다") + void executeStand_good() { + //given + MultiPlayers testMultiPlayers = MultiPlayers.of(TEST_PLAYER_NAMES, totalDeck); + Player testPlayer = testMultiPlayers.findNotStayPlayer().get(); + + //when + Player result = testMultiPlayers.executeStand(testPlayer); + + //then + assertEquals(Player.class, result.getClass()); + } + } + + @Test + @DisplayName("초기 상태를 잘 가져온다") + void getInitialState_success() { + //given + String testerName = "pobi"; + Card heartTen = new Card(CardShape.하트, CardContents.TEN); + Card heartJ = new Card(CardShape.하트, CardContents.J); + Queue onlyTwoTenCards = new LinkedList<>(List.of(heartTen, heartJ)); + List onlyOneNames = List.of(testerName); + CardCreationStrategy onlyTwoTenCardsCreationStrategy = () -> new ArrayDeque<>(onlyTwoTenCards); + MultiPlayers multiPlayers = MultiPlayers.of(onlyOneNames, Deck.createDeck(onlyTwoTenCardsCreationStrategy)); + ParticipantDto expect = ParticipantDto.from( + Player.from(testerName, Hand.of(heartTen, heartJ)) + ); + + //when + List result = multiPlayers.getInitialStates(); + + //then + Assertions.assertEquals(expect, result.getFirst()); + } + + @Test + @DisplayName("게임 결과를 잘 가져온다") + void checkPlayersGameResult_success() { + //given + String testerName = "pobi"; + List onlyOneNames = List.of(testerName); + + Card heartTwo = new Card(CardShape.하트, CardContents.TWO); + Card heartThree = new Card(CardShape.하트, CardContents.THREE); + Card heartTen = new Card(CardShape.하트, CardContents.TEN); + Card heartJ = new Card(CardShape.하트, CardContents.J); + Queue onlyTwoTenCards = new LinkedList<>(List.of(heartTen, heartJ)); + + Dealer testDealer = Dealer.from(Hand.of(heartTwo, heartThree)); + Player expectedPlayer = Player.from(testerName, Hand.of(heartTen, heartJ)); + CardCreationStrategy onlyTwoTenCardsCreationStrategy = () -> new ArrayDeque<>(onlyTwoTenCards); + MultiPlayers multiPlayers = MultiPlayers.of(onlyOneNames, Deck.createDeck(onlyTwoTenCardsCreationStrategy)); + PlayerResultDto expect = PlayerResultDto.from(expectedPlayer, testDealer); + + //when + List result = multiPlayers.checkPlayersGameResult(testDealer); + + //then + Assertions.assertEquals(expect, result.getFirst()); + } + + + private Deque createSampleCards() { + CardShape[] shapes = CardShape.values(); + CardContents[] contents = CardContents.values(); + + List sampleCards = new ArrayList<>(); + for (CardShape cardShape : shapes) { + for (CardContents content : contents) { + sampleCards.add(new Card(cardShape, content)); + } + } + + return new ArrayDeque<>(sampleCards); + } + +} diff --git a/src/test/java/domain/PlayersTest.java b/src/test/java/domain/PlayersTest.java deleted file mode 100644 index 5b52b6344cb..00000000000 --- a/src/test/java/domain/PlayersTest.java +++ /dev/null @@ -1,120 +0,0 @@ -package domain; - -import static org.assertj.core.api.AssertionsForClassTypes.assertThat; -import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; - -import common.ErrorMessage; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Deque; -import java.util.List; -import java.util.Optional; -import org.junit.jupiter.api.Assertions; -import org.junit.jupiter.api.BeforeEach; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Nested; -import org.junit.jupiter.api.Test; - -class PlayersTest { - private static final List TEST_PLAYER_NAMES = List.of("pobi", "terry", "rati", "gump"); - - - Deck totalDeck; - - @BeforeEach - void setUpTotalDeck() { - CardCreationStrategy totalCardCreationStrategy = this::createSampleCards; - totalDeck = Deck.createDeck(totalCardCreationStrategy); - } - - @Nested - class ofTest { - @Test - @DisplayName("생성 잘 한다") - void of_good() { - //when, then - assertDoesNotThrow( - () -> Players.of(TEST_PLAYER_NAMES, totalDeck) - ); - } - - @Test - @DisplayName("이름이 중복되면 오류가 발생한다") - void of_fail_duplication() { - //given - List testPlayerNames = List.of("pobi", "pobi"); - - //when && then - assertThatThrownBy(() -> Players.of(testPlayerNames, totalDeck)) - .isInstanceOf(IllegalArgumentException.class) - .hasMessageContaining(ErrorMessage.NAME_UNIQUENESS_ERR.getMessage()); - } - } - - @Test - @DisplayName("사용자의 상태에 따라 Turn이 변경된다") - void next_change_targetUser() { - //given - Players testPlayers = Players.of(TEST_PLAYER_NAMES, totalDeck); - Player prevPlayer = testPlayers.findNotStayPlayer().get(); - prevPlayer = prevPlayer.stand(); - - //when, then - Assertions.assertNotEquals(prevPlayer, testPlayers.findNotStayPlayer()); - } - - @Test - @DisplayName("사용자가 stay가 되면 더 이상 현재 사용자로 조회되지 않는다") - void executeStand_success() { - // given - String rati = "rati"; - String pobi = "pobi"; - Players players = Players.of(List.of(rati, pobi), totalDeck); - Player currentPlayer = players.findNotStayPlayer().get(); - - // when - players.executeStand(currentPlayer); - - // then - Optional nextUser = players.findNotStayPlayer(); - assertThat(nextUser.isPresent()).isTrue(); - assertThat(nextUser.get().getName()).isEqualTo(pobi); - - players.executeStand(nextUser.get()); - assertThat(players.findNotStayPlayer()).isEmpty(); - } - - // @Test -// @DisplayName("getDecksPerUser에서 잘 가져온다") -// void getDecksPerUser_success() { -// //given -// List testPlayerNames = List.of("pobi"); -// List expectPobiCards = List.of( -// new Card(CardShape.스페이드, CardContents.A), -// new Card(CardShape.스페이드, CardContents.TWO) -// ); -// -// //when -// Players players = Players.of(testPlayerNames, totalDeck); -// Map> result = players.getDecksPerPlayer(); -// -// //then -// assertEquals(expectPobiCards, result.get("pobi")); -// } -// - private Deque createSampleCards() { - CardShape[] shapes = CardShape.values(); - CardContents[] contents = CardContents.values(); - - List sampleCards = new ArrayList<>(); - for (CardShape cardShape : shapes) { - for (CardContents content : contents) { - sampleCards.add(new Card(cardShape, content)); - } - } - - return new ArrayDeque<>(sampleCards); - } - -} From 7cffcf550064fb6b9af848a6afd74b874c72ec5a Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Thu, 12 Mar 2026 12:16:29 +0900 Subject: [PATCH 101/129] fix : uncomment test logic of Deck MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 주석 처리 해제 - 사라진 메서드의 테스트 로직 제거 - 추가된 drawTwoCards 메서드 테스트 추가 --- src/main/java/domain/Deck.java | 2 - src/test/java/domain/DeckTest.java | 113 ++++++++++------------------- 2 files changed, 38 insertions(+), 77 deletions(-) diff --git a/src/main/java/domain/Deck.java b/src/main/java/domain/Deck.java index 1e64c6ef503..935c2bf1eca 100644 --- a/src/main/java/domain/Deck.java +++ b/src/main/java/domain/Deck.java @@ -16,8 +16,6 @@ public static Deck createDeck(CardCreationStrategy strategy) { return new Deck(strategy.create()); } - // 메시지 : 카드를 만들어줘. - public Card drawCard() { try { return cards.pop(); diff --git a/src/test/java/domain/DeckTest.java b/src/test/java/domain/DeckTest.java index c18d6934988..a81a0ceaf1d 100644 --- a/src/test/java/domain/DeckTest.java +++ b/src/test/java/domain/DeckTest.java @@ -1,12 +1,17 @@ package domain; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertTrue; +import common.ErrorMessage; import java.util.ArrayDeque; import java.util.Deque; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; class DeckTest { @@ -33,79 +38,37 @@ void deck_create_success() { () -> Deck.createDeck(fixedCardCreationStrategy) ); } -// -// @Test -// @DisplayName("카드들의 점수 합을 구함") -// void calculate_card_score_sum() { -// CardCreationStrategy fixedCardCreationStrategy = new CardCreationStrategy() { -// @Override -// public Deque create() { -// Card spadeJ = new Card(CardShape.스페이드, CardContents.J); -// Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); -// Card diamondAce = new Card(CardShape.다이아몬드, CardContents.A); -// -// return List.of(spadeJ, clover5, diamondAce); -// } -// }; -// Deck deck = Deck.createDeck(fixedCardCreationStrategy); -// Deck deck = Deck.createDeck(fixedCardCreationStrategy); -// assertThat(deck.calculateCardScoreSum()).isEqualTo(16); -// } -// -// @Test -// @DisplayName("덱에 카드 한장을 추가함") -// void add_card() { -// // given -// CardCreationStrategy gameStrategy = new CardCreationStrategy() { -// @Override -// public List create() { -// Card spadeJ = new Card(CardShape.스페이드, CardContents.J); -// Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); -// Card diamond3 = new Card(CardShape.다이아몬드, CardContents.THREE); -// -// return new ArrayList<>(List.of(spadeJ, clover5, diamond3)); -// } -// }; -// Deck gameDeck = Deck.createDeck(gameStrategy); -// -// CardCreationStrategy playerStrategy = new CardCreationStrategy() { -// @Override -// public List create() { -// Card spadeA = new Card(CardShape.스페이드, CardContents.A); -// -// return new ArrayList<>(List.of(spadeA)); -// } -// }; -// Deck playerDeck = Deck.createDeck(playerStrategy); -// -// // when -// int result = playerDeck.addCard(gameDeck.drawCard()); -// int expected = 2; -// -// // then -// assertThat(result).isEqualTo(expected); -// } -// -// @Nested -// class drawTest { -// @Test -// @DisplayName("Deck에서 카드를 한장 뽑아줌") -// void draw_card_success() { -// Card result = deck.drawCard(); -// Card expected = new Card(CardShape.스페이드, CardContents.J); -// assertThat(result).isEqualTo(expected); -// } -// -// @Test -// @DisplayName("Deck에서 0이하 혹은 남은 카드 이상의 숫자 선택 시도 시 오류 발생") -// void draw_card_fail() { -// deck.drawCard(); -// deck.drawCard(); -// -// assertThatThrownBy( -// () -> deck.drawCard() -// ).isInstanceOf(IllegalArgumentException.class) -// .hasMessage(ErrorMessage.DRAW_CARD_OUT_OF_RANGE.getMessage()); -// } -// } + + @Nested + class drawTest { + @Test + @DisplayName("Deck에서 카드를 한장 뽑아줌") + void draw_card_success() { + Card result = deck.drawCard(); + Card expected = new Card(CardShape.스페이드, CardContents.J); + assertThat(result).isEqualTo(expected); + } + + @Test + @DisplayName("Deck에서 카드를 두장 뽑아줌") + void draw_two_cards_success() { + List result = deck.drawTwoCards(); + Card expectedCard1 = new Card(CardShape.스페이드, CardContents.J); + Card expectedCard2 = new Card(CardShape.클로버, CardContents.FIVE); + + assertTrue(result.contains(expectedCard1)); + assertTrue(result.contains(expectedCard2)); + } + + @Test + @DisplayName("Deck에서 0이하 혹은 남은 카드 이상의 숫자 선택 시도 시 오류 발생") + void draw_card_fail() { + deck.drawTwoCards(); + + assertThatThrownBy( + () -> deck.drawCard() + ).isInstanceOf(IllegalArgumentException.class) + .hasMessage(ErrorMessage.DRAW_CARD_OUT_OF_RANGE.getMessage()); + } + } } \ No newline at end of file From cfe89d77ab4fd1771d79b89728a4a626844990a4 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Thu, 12 Mar 2026 13:30:11 +0900 Subject: [PATCH 102/129] fix : uncomment addCard test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존 코드 주석 해제 - 이전의 변경에 따른 테스트 로직 내부 메서드 이름 수정 --- src/test/java/domain/DealerTest.java | 86 +++++++++++----------------- 1 file changed, 32 insertions(+), 54 deletions(-) diff --git a/src/test/java/domain/DealerTest.java b/src/test/java/domain/DealerTest.java index 4f0bf656042..b6c5027c673 100644 --- a/src/test/java/domain/DealerTest.java +++ b/src/test/java/domain/DealerTest.java @@ -1,69 +1,47 @@ package domain; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertTrue; +import java.util.ArrayDeque; +import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; public class DealerTest { + + private static Hand dealerHand = Hand.of( + new Card(CardShape.스페이드, CardContents.J), + new Card(CardShape.클로버, CardContents.FIVE) + ); + @Test @DisplayName("Dealer를 생성할 때 오류 발생 안함") void dealer_create_success() { - Hand dealerHand = Hand.of( - new Card(CardShape.스페이드, CardContents.J), - new Card(CardShape.클로버, CardContents.FIVE) - ); - assertDoesNotThrow(() -> Dealer.from(dealerHand)); } -// @Test -// @DisplayName("딜러는 카드의 합이 16 이하면 카드를 한 장 더 받는다") -// void addCard_Dealer_success() { -// //given -// CardCreationStrategy dealerCardCreationStrategy = () -> { -// Card spadeJ = new Card(CardShape.스페이드, CardContents.J); -// return new ArrayList<>(List.of(spadeJ)); -// }; -// CardCreationStrategy totalCardCreationStrategy = () -> { -// Card spadeA = new Card(CardShape.스페이드, CardContents.A); -// Card heartA = new Card(CardShape.하트, CardContents.TWO); -// return new ArrayList<>(List.of(spadeA, heartA)); -// }; -// Deck dealerDeck = Deck.createDeck(dealerCardCreationStrategy); -// Deck totalDeck = Deck.createDeck(totalCardCreationStrategy); -// Dealer dealer = new Dealer(dealerDeck); -// -// //when -// Optional result = dealer.addCard(totalDeck); -// -// //then -// Assertions.assertThat(result.isPresent()).isTrue(); -// } -// -// -// @Test -// @DisplayName("딜러는 카드의 합이 16 이상 -> Optional를 반환") -// void addCard_Dealer_Optional_empty() { -// //given -// CardCreationStrategy dealerCardCreationStrategy = () -> { -// Card spadeJ = new Card(CardShape.스페이드, CardContents.J); -// Card heartJ = new Card(CardShape.하트, CardContents.J); -// return new ArrayList<>(List.of(spadeJ, heartJ)); -// }; -// CardCreationStrategy totalCardCreationStrategy = () -> { -// Card spadeA = new Card(CardShape.스페이드, CardContents.A); -// Card heartA = new Card(CardShape.하트, CardContents.TWO); -// return new ArrayList<>(List.of(spadeA, heartA)); -// }; -// Deck dealerDeck = Deck.createDeck(dealerCardCreationStrategy); -// Deck totalDeck = Deck.createDeck(totalCardCreationStrategy); -// Dealer dealer = new Dealer(dealerDeck); -// -// //when -// Optional result = dealer.addCard(totalDeck); -// -// //then -// Assertions.assertThat(result.isPresent()).isFalse(); -// } + @Test + @DisplayName("딜러는 카드의 합이 16 이하면 카드를 한 장 더 받고 새로운 Dealer를 반환한다") + void addCard_success() { + //given + CardCreationStrategy onlyJCardCreation = () -> { + Card spadeA = new Card(CardShape.스페이드, CardContents.J); + return new ArrayDeque<>(List.of(spadeA)); + }; + Deck totalDeck = Deck.createDeck(onlyJCardCreation); + + Hand dealerHandSixteen = Hand.of( + new Card(CardShape.하트, CardContents.SIX), + new Card(CardShape.스페이드, CardContents.TEN) + ); + Dealer dealer = Dealer.from(dealerHandSixteen); + + //when + Optional result = dealer.addCard(totalDeck::drawCard); + + //then + assertTrue(result.isPresent()); + } } From 0a47bad92b96d6250c281411bb1e28a257329673 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Thu, 12 Mar 2026 13:56:40 +0900 Subject: [PATCH 103/129] fix : change decide logic and add test --- src/main/java/domain/GameResult.java | 22 +-- src/test/java/domain/GameResultTest.java | 169 +++++++++++++++++++++++ 2 files changed, 182 insertions(+), 9 deletions(-) create mode 100644 src/test/java/domain/GameResultTest.java diff --git a/src/main/java/domain/GameResult.java b/src/main/java/domain/GameResult.java index b2d5ec4165b..8f9faebfda4 100644 --- a/src/main/java/domain/GameResult.java +++ b/src/main/java/domain/GameResult.java @@ -3,19 +3,23 @@ public enum GameResult { 승, 무, 패; - public static GameResult determinePlayerResult( - int dealerScore, - int playerScore, - boolean isDealerBust, - boolean isPlayerBust - ) { - if (isDealerBust) { + public static GameResult decidePlayerResult(Dealer dealer, Player player) { + if (player.isBust()) { + return GameResult.패; + } + if (dealer.isBust()) { + return GameResult.승; + } + if (player.isBlackJack() && dealer.isBlackJack()) { + return GameResult.무; + } + if (player.isBlackJack()) { return GameResult.승; } - if (isPlayerBust) { + if (dealer.isBlackJack()) { return GameResult.패; } - return compareScoreForCheckPlayerResult(dealerScore, playerScore); + return compareScoreForCheckPlayerResult(dealer.getOwnCardsSum(), player.getOwnCardsSum()); } private static GameResult compareScoreForCheckPlayerResult(int dealerScore, int playerScore) { diff --git a/src/test/java/domain/GameResultTest.java b/src/test/java/domain/GameResultTest.java new file mode 100644 index 00000000000..effc02190b1 --- /dev/null +++ b/src/test/java/domain/GameResultTest.java @@ -0,0 +1,169 @@ +package domain; + +import static org.junit.jupiter.api.Assertions.assertEquals; + +import java.util.ArrayDeque; +import java.util.LinkedList; +import java.util.List; +import java.util.Optional; +import java.util.Queue; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class GameResultTest { + Hand normalHand = Hand.of( + new Card(CardShape.다이아몬드, CardContents.A), + new Card(CardShape.다이아몬드, CardContents.TWO) + ); + Hand blackJackHand = Hand.of( + new Card(CardShape.클로버, CardContents.A), + new Card(CardShape.클로버, CardContents.TEN) + ); + Hand handThatValue16 = Hand.of( + new Card(CardShape.스페이드, CardContents.TEN), + new Card(CardShape.스페이드, CardContents.SIX) + ); + + @Nested + class decidePlayerResultTest { + @Nested + class blackJackCaseTest { + + @Test + @DisplayName("플레이어가 블랙잭이면 플레이어가 이긴 결과를 도출한다") + void decidePlayerResult_player_blackjack() { + //given + Dealer testDealer = Dealer.from( + normalHand + ); + Player testPlayer = Player.from( + "gump", + blackJackHand + ); + + //when + GameResult result = GameResult.decidePlayerResult(testDealer, testPlayer); + + //then + assertEquals(GameResult.승, result); + } + + @Test + @DisplayName("딜러가 블랙잭이면 플레이어가 진 결과를 도출한다") + void decidePlayerResult_dealer_blackjack() { + //given + Dealer testDealer = Dealer.from( + blackJackHand + ); + Player testPlayer = Player.from( + "gump", + normalHand + ); + + //when + GameResult result = GameResult.decidePlayerResult(testDealer, testPlayer); + + //then + assertEquals(GameResult.패, result); + } + + @Test + @DisplayName("딜러, 플레이어 둘 다가 블랙잭이면 플레이어가 무승부라는 결과를 도출한다") + void decidePlayerResult_both_blackjack() { + //given + Hand anotherBlackJackHand = Hand.of( + new Card(CardShape.하트, CardContents.A), + new Card(CardShape.하트, CardContents.TEN) + ); + Dealer testDealer = Dealer.from( + blackJackHand + ); + Player testPlayer = Player.from( + "gump", + anotherBlackJackHand + ); + + //when + GameResult result = GameResult.decidePlayerResult(testDealer, testPlayer); + + //then + assertEquals(GameResult.무, result); + } + } + + @Nested + class bustCaseTest { + Queue onlyTwoTenCards = new LinkedList<>( + List.of( + new Card(CardShape.다이아몬드, CardContents.TEN), + new Card(CardShape.다이아몬드, CardContents.Q) + ) + ); + CardCreationStrategy testStrategy = () -> new ArrayDeque<>(onlyTwoTenCards); + Deck testTotalDeck = Deck.createDeck(testStrategy); + + @Test + @DisplayName("플레이어가 bust 이면 패 결과를 반환") + void player_bust_case() { + //given + Dealer testDealer = Dealer.from( + normalHand + ); + Player testPlayer = Player.from( + "gump", + handThatValue16 + ); + testPlayer = testPlayer.hit(testTotalDeck::drawCard); + + //when + GameResult result = GameResult.decidePlayerResult(testDealer, testPlayer); + + //then + assertEquals(GameResult.패, result); + } + + @Test + @DisplayName("딜러가 bust 이면 승 결과를 반환") + void dealer_bust_case() { + //given + Dealer testDealer = Dealer.from( + handThatValue16 + ); + Player testPlayer = Player.from( + "gump", + normalHand + ); + Optional dealer = testDealer.addCard(testTotalDeck::drawCard); + + //when + GameResult result = GameResult.decidePlayerResult(dealer.get(), testPlayer); + + //then + assertEquals(GameResult.승, result); + } + } + } + + @Nested + class reverseTest { + @Test + @DisplayName("승 -> 패") + void reverseToLose() { + assertEquals(GameResult.패, GameResult.승.reverse()); + } + + @Test + @DisplayName("패 -> 승") + void reverseToWin() { + assertEquals(GameResult.승, GameResult.패.reverse()); + } + + @Test + @DisplayName("무 -> 무") + void reverseToDraw() { + assertEquals(GameResult.무, GameResult.무.reverse()); + } + } + +} \ No newline at end of file From 7ad4553ba745498658b30a6ab386935e039a8449 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Thu, 12 Mar 2026 13:59:32 +0900 Subject: [PATCH 104/129] test : add equal override test --- src/test/java/domain/CardTest.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/test/java/domain/CardTest.java b/src/test/java/domain/CardTest.java index 815aa5c4427..6c07efe0d25 100644 --- a/src/test/java/domain/CardTest.java +++ b/src/test/java/domain/CardTest.java @@ -61,4 +61,20 @@ void isAce_false() { assertFalse(result); } } + + @Test + @DisplayName("컨텐츠가 같으면 같은 카드로 인식한다") + void same_content_same_card() { + //given + CardContents aceCardContents = CardContents.A; + CardShape cardShapeHeart = CardShape.하트; + Card card1 = new Card(cardShapeHeart, aceCardContents); + Card card2 = new Card(cardShapeHeart, aceCardContents); + + //when + boolean result = card1.equals(card2); + + //then + assertTrue(result); + } } From fc321522e782943cb297d94d4b75bf103f4d765c Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Thu, 12 Mar 2026 14:00:23 +0900 Subject: [PATCH 105/129] fix : update readabillity of outputView --- src/main/java/view/OutputView.java | 153 ++++++++++++----------------- 1 file changed, 65 insertions(+), 88 deletions(-) diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 47030b3bcfa..db41eea33a5 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,20 +1,24 @@ package view; +import domain.GameResult; import dto.CardDto; +import dto.DealerResultDto; +import dto.GameResultDto; import dto.ParticipantDto; -import java.util.ArrayList; +import dto.PlayerResultDto; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; public class OutputView { private static final String DELIMITER = ", "; private static final String NAME_PROMPT = "게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)"; - private static final String INITIAL_CARD_SHARE = "딜러와 %s에게 2장을 나누었습니다.\n"; + private static final String INITIAL_CARD_SHARE = "\n딜러와 %s에게 2장을 나누었습니다.\n"; private static final String HIT_OR_STAND_PROMPT = "%s는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)\n"; - private static final String PARTICIPANT_CARD_INFO_FORMAT_LINE = "%s카드: %s\n"; private static final String DEALER_ADD_CARD_NOTICE = "\n딜러는 16이하라 한장의 카드를 더 받았습니다.\n"; private static final String PARTICIPANT_CARD_INFO_FORMAT = "%s카드: %s"; + private static final String WIN_LOSS_RESULT_HEADER = "\n## 최종 승패"; private static final String PARTICIPANT_CARD_INFO_WITH_SUM_FORMAT = "%s - 결과: %d\n"; - private static final String WIN_LOSS_RESULT_HEADER = "## 최종 승패\n"; private static final String WIN_LOSS_RESULT_FORMAT = "%s: %s\n"; public void printErrorMessage(Exception e) { @@ -26,21 +30,16 @@ public void printNamePrompt() { } public void printInitialStates(ParticipantDto dealerDto, List players) { - List playerNames = new ArrayList<>(); - StringBuilder initialStates = new StringBuilder(); - - String dealerCardInfo = consistCardInfo(dealerDto); - initialStates.append(dealerCardInfo); - - for (ParticipantDto playerDto : players) { - playerNames.add(playerDto.name()); - String playerCardInfo = consistCardInfo(playerDto); - initialStates.append(playerCardInfo); - } + String playerNames = players.stream() + .map(ParticipantDto::name) + .collect(Collectors.joining(DELIMITER)); + System.out.printf(INITIAL_CARD_SHARE, playerNames); + + printUserState(dealerDto); + players.forEach(this::printUserState); + System.out.println(); - System.out.printf(INITIAL_CARD_SHARE, String.join(DELIMITER, playerNames)); - System.out.println(initialStates); } public void printHitOrStandPrompt(String name) { @@ -48,89 +47,67 @@ public void printHitOrStandPrompt(String name) { } public void printUserState(ParticipantDto participantDto) { - String userCardInfo = consistCardInfo(participantDto); - System.out.println(userCardInfo); + System.out.println(formatParticipantCards(participantDto)); } public void printDealerAddCardNotice() { System.out.println(DEALER_ADD_CARD_NOTICE); } - // -// public void printCardInfoWithSum(GameResultDto gameResultDto) { -// StringBuilder cardInfoWithSum = new StringBuilder(); -// -// ParticipantDto dealerDto = gameResultDto.dealerDto(); -// String dealerCardInfoWithSum = consistCardInfoWithSum(dealerDto); -// cardInfoWithSum.append(dealerCardInfoWithSum); -// -// for (ParticipantDto playerDto : gameResultDto.playerDtos()) { -// String playerCardInfoWithSum = consistCardInfoWithSum(playerDto); -// cardInfoWithSum.append(playerCardInfoWithSum); -// } -// -// System.out.println(); -// System.out.println(cardInfoWithSum); -// } -// -// public void printWinLossResult(GameResultDto gameResultDto) { -// StringBuilder winLossResult = new StringBuilder(); -// winLossResult.append(WIN_LOSS_RESULT_HEADER); -// -// String dealerResult = consistDealerResult(gameResultDto); -// winLossResult.append(dealerResult); -// -// String playerResults = consistPlayerResults(gameResultDto); -// winLossResult.append(playerResults); -// -// System.out.println(winLossResult); -// } -// - private String consistCardInfo(ParticipantDto participantDto) { - List ownCards = participantDto.cards(); - List cardsInformation = extractCardsInfo(ownCards); + public void printGameResult(GameResultDto gameResultDto) { + printFinalStates(gameResultDto); + printFinalWinLoss(gameResultDto); + } + + private void printFinalStates(GameResultDto gameResultDto) { + DealerResultDto dealerResultDto = gameResultDto.dealerResultDto(); + printParticipantFinalState(dealerResultDto.dealerDto(), dealerResultDto.score()); + + List playerResultDtos = gameResultDto.playerResultDto(); + playerResultDtos.forEach(dto -> printParticipantFinalState(dto.playerDto(), dto.score())); + } + + private void printParticipantFinalState(ParticipantDto participantDto, int score) { + System.out.printf(PARTICIPANT_CARD_INFO_WITH_SUM_FORMAT, formatParticipantCards(participantDto), score); + } - String cardsInformationText = String.join(DELIMITER, cardsInformation); + private void printFinalWinLoss(GameResultDto gameResultDto) { + System.out.println(WIN_LOSS_RESULT_HEADER); + + DealerResultDto dealerResultDto = gameResultDto.dealerResultDto(); + System.out.print(formatDealerResult(dealerResultDto)); - return String.format( - PARTICIPANT_CARD_INFO_FORMAT_LINE, - participantDto.name(), - cardsInformationText + List playerResultDtos = gameResultDto.playerResultDto(); + playerResultDtos.forEach(dto -> + System.out.printf(WIN_LOSS_RESULT_FORMAT, dto.playerDto().name(), dto.result().name()) ); } - private List extractCardsInfo(List cards) { + private String formatParticipantCards(ParticipantDto participantDto) { + List ownCards = participantDto.cards(); + return String.format(PARTICIPANT_CARD_INFO_FORMAT, participantDto.name(), formatCardsInfo(ownCards)); + } + + private String formatCardsInfo(List cards) { return cards.stream() .map(card -> card.cardContentNumber() + card.cardShape()) - .toList(); + .collect(Collectors.joining(DELIMITER)); + } + + private String formatDealerResult(DealerResultDto dealerResultDto) { + String dealerName = dealerResultDto.dealerDto().name(); + String resultString = formatDealerResultInfo(dealerResultDto.dealerResult()); + return String.format(WIN_LOSS_RESULT_FORMAT, dealerName, resultString); + } + + private String formatDealerResultInfo(Map dealerGameResult) { + StringBuilder sb = new StringBuilder(); + for (GameResult result : GameResult.values()) { + int count = dealerGameResult.getOrDefault(result, 0); + if (count > 0) { + sb.append(count).append(result.name()).append(" "); + } + } + return sb.toString().trim(); } -// -// private String consistCardInfoWithSum(ParticipantDto participantDto) { -// return String.format(PARTICIPANT_CARD_INFO_WITH_SUM_FORMAT, -// consistCardInfo(PARTICIPANT_CARD_INFO_FORMAT, participantDto), participantDto.score()); -// } -// -// private String consistDealerResult(GameResultDto gameResultDto) { -// String dealerName = gameResultDto.dealerDto().name(); -// Map dealerWinLossResults = gameResultDto.dealerWinLossResults(); -// StringBuilder result = new StringBuilder(); -// for (Entry dealerResult : dealerWinLossResults.entrySet()) { -// result.append(dealerResult.getValue()); -// result.append(dealerResult.getKey()); -// result.append(" "); -// } -// result.deleteCharAt(result.length() - 1); -// return String.format(WIN_LOSS_RESULT_FORMAT, dealerName, result); -// } -// -// private String consistPlayerResults(GameResultDto gameResultDto) { -// Map playerWinLossResults = gameResultDto.playerWinLossResults(); -// StringBuilder result = new StringBuilder(); -// for (Entry playerResult : playerWinLossResults.entrySet()) { -// String playerName = playerResult.getKey(); -// String playerWinLossResult = playerResult.getValue(); -// result.append(String.format(WIN_LOSS_RESULT_FORMAT, playerName, playerWinLossResult)); -// } -// return result.toString(); -// } } From f3c5cddb7f15b76165f835f35ba11c5dc41438e1 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Thu, 12 Mar 2026 14:00:47 +0900 Subject: [PATCH 106/129] fix : update dto classes to upgrade readabillity --- src/main/java/dto/DealerResultDto.java | 32 ++++++++++++++ src/main/java/dto/GameResultDto.java | 59 +++++++++++--------------- src/main/java/dto/PlayerResultDto.java | 19 +++++++++ 3 files changed, 75 insertions(+), 35 deletions(-) create mode 100644 src/main/java/dto/DealerResultDto.java create mode 100644 src/main/java/dto/PlayerResultDto.java diff --git a/src/main/java/dto/DealerResultDto.java b/src/main/java/dto/DealerResultDto.java new file mode 100644 index 00000000000..0a1748b4ded --- /dev/null +++ b/src/main/java/dto/DealerResultDto.java @@ -0,0 +1,32 @@ +package dto; + +import domain.Dealer; +import domain.GameResult; +import java.util.EnumMap; +import java.util.List; +import java.util.Map; + +public record DealerResultDto( + ParticipantDto dealerDto, + int score, + Map dealerResult +) { + public static DealerResultDto from(Dealer dealer, List playerResults) { + Map totalResult = initializeTotalResult(); + + playerResults.stream() + .map(PlayerResultDto::result) + .map(GameResult::reverse) + .forEach(result -> totalResult.merge(result, 1, Integer::sum)); + + return new DealerResultDto(ParticipantDto.from(dealer), dealer.getOwnCardsSum(), totalResult); + } + + private static Map initializeTotalResult() { + Map map = new EnumMap<>(GameResult.class); + for (GameResult result : GameResult.values()) { + map.put(result, 0); + } + return map; + } +} \ No newline at end of file diff --git a/src/main/java/dto/GameResultDto.java b/src/main/java/dto/GameResultDto.java index 9acc52541e5..86ddc9694cb 100644 --- a/src/main/java/dto/GameResultDto.java +++ b/src/main/java/dto/GameResultDto.java @@ -1,41 +1,30 @@ package dto; -import domain.Dealer; -import domain.Player; -import domain.Players; -import domain.Result; -import java.util.ArrayList; -import java.util.LinkedHashMap; import java.util.List; -import java.util.Map; -import java.util.Map.Entry; -public record GameResultDto(ParticipantDto dealerDto, - List playerDtos, - Map dealerWinLossResults, - Map playerWinLossResults) { +public record GameResultDto(DealerResultDto dealerResultDto, List playerResultDto) { - public static GameResultDto from(Dealer dealer, Players players, - Map dealerWinLossResults, - Map playerWinLossResults) { - - ParticipantDto dealerDto = ParticipantDto.from(dealer); - - List playerDtos = new ArrayList<>(); - for (Player player : players.getPlayers()) { - playerDtos.add(ParticipantDto.from(player)); - } - - Map dealerResults = new LinkedHashMap<>(); - for (Entry entry : dealerWinLossResults.entrySet()) { - dealerResults.put(entry.getKey().name(), entry.getValue()); - } - - Map playerResults = new LinkedHashMap<>(); - for (Entry entry : playerWinLossResults.entrySet()) { - playerResults.put(entry.getKey().getName(), entry.getValue().name()); - } - - return new GameResultDto(dealerDto, playerDtos, dealerResults, playerResults); - } +// public static GameResultDto from(Dealer dealer, Players players, +// Map dealerWinLossResults, +// Map playerWinLossResults) { +// +// ParticipantDto dealerDto = ParticipantDto.from(dealer); +// +// List playerDtos = new ArrayList<>(); +// for (Player player : players.getPlayers()) { +// playerDtos.add(ParticipantDto.from(player)); +// } +// +// Map dealerResults = new LinkedHashMap<>(); +// for (Entry entry : dealerWinLossResults.entrySet()) { +// dealerResults.put(entry.getKey().name(), entry.getValue()); +// } +// +// Map playerResults = new LinkedHashMap<>(); +// for (Entry entry : playerWinLossResults.entrySet()) { +// playerResults.put(entry.getKey().getName(), entry.getValue().name()); +// } +// +// return new GameResultDto(dealerDto, playerDtos, dealerResults, playerResults); +// } } diff --git a/src/main/java/dto/PlayerResultDto.java b/src/main/java/dto/PlayerResultDto.java new file mode 100644 index 00000000000..063362e620b --- /dev/null +++ b/src/main/java/dto/PlayerResultDto.java @@ -0,0 +1,19 @@ +package dto; + +import domain.Dealer; +import domain.GameResult; +import domain.Player; + +public record PlayerResultDto( + ParticipantDto playerDto, + int score, + GameResult result +) { + public static PlayerResultDto from(Player player, Dealer dealer) { + return new PlayerResultDto( + ParticipantDto.from(player), + player.getOwnCardsSum(), + player.compare(dealer) + ); + } +} From bfd9c37a9bf96dcbe415682494dbff66f3292ea8 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Thu, 12 Mar 2026 14:01:20 +0900 Subject: [PATCH 107/129] fix : rename to BlackJackGame --- src/main/java/domain/BlackJackGame.java | 95 +++----------- src/main/java/domain/Game.java | 103 --------------- src/test/java/domain/BlackJackGameTest.java | 132 +++++++++++++++++++ src/test/java/domain/GameTest.java | 133 -------------------- 4 files changed, 152 insertions(+), 311 deletions(-) delete mode 100644 src/main/java/domain/Game.java create mode 100644 src/test/java/domain/BlackJackGameTest.java delete mode 100644 src/test/java/domain/GameTest.java diff --git a/src/main/java/domain/BlackJackGame.java b/src/main/java/domain/BlackJackGame.java index 3e23470f10e..97b60d942d5 100644 --- a/src/main/java/domain/BlackJackGame.java +++ b/src/main/java/domain/BlackJackGame.java @@ -1,19 +1,22 @@ package domain; import common.ErrorMessage; +import dto.DealerResultDto; +import dto.GameResultDto; import dto.ParticipantDto; +import dto.PlayerResultDto; import java.util.List; import java.util.Optional; public class BlackJackGame { private final Deck totalDeck; - private final Players players; + private final MultiPlayers multiPlayers; private Dealer dealer; - private BlackJackGame(Deck totalDeck, Dealer dealer, Players players) { + private BlackJackGame(Deck totalDeck, Dealer dealer, MultiPlayers multiPlayers) { this.totalDeck = totalDeck; this.dealer = dealer; - this.players = players; + this.multiPlayers = multiPlayers; } public static BlackJackGame ready(List playerNames, CardCreationStrategy strategy) { @@ -21,7 +24,7 @@ public static BlackJackGame ready(List playerNames, CardCreationStrategy return new BlackJackGame( totalDeck, createNewDealer(totalDeck), - Players.of(playerNames, totalDeck) + MultiPlayers.of(playerNames, totalDeck) ); } @@ -33,19 +36,19 @@ private static Dealer createNewDealer(Deck totalDeck) { } public Optional whoseTurn() { - return players.findNotStayPlayer(); + return multiPlayers.findNotStayPlayer(); } public ParticipantDto doHitProcess() { - Player newPlayer = players.findNotStayPlayer() - .map(player -> players.executeHit(player, totalDeck::drawCard)) + Player newPlayer = multiPlayers.findNotStayPlayer() + .map(player -> multiPlayers.executeHit(player, totalDeck::drawCard)) .orElseThrow(() -> new IllegalStateException(ErrorMessage.NO_MORE_PLAYABLE_PLAYER.getMessage())); return ParticipantDto.from(newPlayer); } public ParticipantDto doStandProcess() { - Player newPlayer = players.findNotStayPlayer() - .map(players::executeStand) + Player newPlayer = multiPlayers.findNotStayPlayer() + .map(multiPlayers::executeStand) .orElseThrow(() -> new IllegalStateException(ErrorMessage.NO_MORE_PLAYABLE_PLAYER.getMessage())); return ParticipantDto.from(newPlayer); } @@ -57,79 +60,21 @@ public boolean doDealerHitOrStandProcess() { } public List getPlayersGameSettingStates() { - return players.getInitialStates(); + return multiPlayers.getInitialStates(); } public ParticipantDto getDealerGameSettingState() { return ParticipantDto.consistWithInitialInfo(dealer); } + public GameResultDto getGameResults() { + List playersGameResults = multiPlayers.checkPlayersGameResult(dealer); + DealerResultDto dealerGameResult = DealerResultDto.from(dealer, playersGameResults); + + return new GameResultDto(dealerGameResult, playersGameResults); + } + private void updateDealer(Dealer newDealer) { this.dealer = newDealer; } -// -// public void play(GameDelegate observer) { -// List individualPlayers = players.getPlayers(); -// for (Player player : individualPlayers) { -// while (!player.isBust() && observer.askDrawCard(player.getName())) { -// player.addCard(totalDeck); -// observer.showPlayerCards(ParticipantDto.from(player)); -// } -// } -// while (dealer.addCard(totalDeck).isPresent()) { -// observer.showDealerOneMoreCardMessage(); -// } -// } -// -// public void end(GameDelegate delegate) { -// GameResultDto result = this.calculateResult(); -// delegate.showGameResult(result); -// } -// -// private GameResultDto calculateResult() { -// int dealerScore = dealer.calculateHandSum(); -// boolean isDealerBust = dealer.isBust(); -// -// Map playerWinLossResults = consistPlayerWinLossResults(dealerScore, isDealerBust); -// Map dealerWinLossResults = consistDealerResult(playerWinLossResults); -// -// return GameResultDto.from( -// dealer, -// players, -// dealerWinLossResults, -// playerWinLossResults -// ); -// } -// -// private Map consistPlayerWinLossResults(int dealerScore, boolean isDealerBust) { -// Map playerWinLossResults = new LinkedHashMap<>(); -// List playingPlayers = players.getPlayers(); -// -// for (Player specificPlayer : playingPlayers) { -// Result specificPlayerResult = determinePlayerResult(dealerScore, isDealerBust, specificPlayer); -// playerWinLossResults.put(specificPlayer, specificPlayerResult); -// } -// -// return playerWinLossResults; -// } -// -// private Result determinePlayerResult(int dealerScore, boolean isDealerBust, Player specificPlayer) { -// return Result.determinePlayerResult( -// dealerScore, -// specificPlayer.calculateHandSum(), -// isDealerBust, -// specificPlayer.isBust() -// ); -// } -// -// private Map consistDealerResult(Map playerWinLossResults) { -// Map dealerWinLossResults = new HashMap<>(); -// List playingPlayers = playerWinLossResults.keySet().stream().toList(); -// for (Player player : playingPlayers) { -// Result dealerResult = playerWinLossResults.get(player).reverse(); -// int currentValue = dealerWinLossResults.getOrDefault(dealerResult, 0); -// dealerWinLossResults.put(dealerResult, currentValue + 1); -// } -// return dealerWinLossResults; -// } } diff --git a/src/main/java/domain/Game.java b/src/main/java/domain/Game.java deleted file mode 100644 index c2371730a9c..00000000000 --- a/src/main/java/domain/Game.java +++ /dev/null @@ -1,103 +0,0 @@ -package domain; - -import controller.GameDelegate; -import dto.GameResultDto; -import dto.ParticipantDto; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; - -public class Game { - private final Deck totalDeck; - private final Dealer dealer; - private final Players players; - - private Game(Deck totalDeck, Dealer dealer, Players players) { - this.totalDeck = totalDeck; - this.dealer = dealer; - this.players = players; - } - - public static Game ready(GameDelegate delegate, CardCreationStrategy strategy) { - Deck totalDeck = Deck.createDeck(strategy); - - Deck dealerDeck = Deck.createParticipantDeck(totalDeck); - Dealer dealer = new Dealer(dealerDeck); - - List playerNames = delegate.askPlayerNames(); - Players players = Players.of(playerNames, totalDeck); - - delegate.showInitialParticipantCards( - ParticipantDto.initialFrom(dealer), - ParticipantDto.listOf(players.getPlayers()) - ); - - return new Game(totalDeck, dealer, players); - } - - public void play(GameDelegate observer) { - List individualPlayers = players.getPlayers(); - for (Player player : individualPlayers) { - while (!player.isBust() && observer.askDrawCard(player.getName())) { - player.addCard(totalDeck); - observer.showPlayerCards(ParticipantDto.from(player)); - } - } - while (dealer.addCard(totalDeck).isPresent()) { - observer.showDealerOneMoreCardMessage(); - } - } - - public void end(GameDelegate delegate) { - GameResultDto result = this.calculateResult(); - delegate.showGameResult(result); - } - - private GameResultDto calculateResult() { - int dealerScore = dealer.calculateDeckSum(); - boolean isDealerBust = dealer.isBust(); - - Map playerWinLossResults = consistPlayerWinLossResults(dealerScore, isDealerBust); - Map dealerWinLossResults = consistDealerResult(playerWinLossResults); - - return GameResultDto.from( - dealer, - players, - dealerWinLossResults, - playerWinLossResults - ); - } - - private Map consistPlayerWinLossResults(int dealerScore, boolean isDealerBust) { - Map playerWinLossResults = new LinkedHashMap<>(); - List playingPlayers = players.getPlayers(); - - for (Player specificPlayer : playingPlayers) { - Result specificPlayerResult = determinePlayerResult(dealerScore, isDealerBust, specificPlayer); - playerWinLossResults.put(specificPlayer, specificPlayerResult); - } - - return playerWinLossResults; - } - - private Result determinePlayerResult(int dealerScore, boolean isDealerBust, Player specificPlayer) { - return Result.determinePlayerResult( - dealerScore, - specificPlayer.calculateDeckSum(), - isDealerBust, - specificPlayer.isBust() - ); - } - - private Map consistDealerResult(Map playerWinLossResults) { - Map dealerWinLossResults = new HashMap<>(); - List playingPlayers = playerWinLossResults.keySet().stream().toList(); - for (Player player : playingPlayers) { - Result dealerResult = playerWinLossResults.get(player).reverse(); - int currentValue = dealerWinLossResults.getOrDefault(dealerResult, 0); - dealerWinLossResults.put(dealerResult, currentValue + 1); - } - return dealerWinLossResults; - } -} diff --git a/src/test/java/domain/BlackJackGameTest.java b/src/test/java/domain/BlackJackGameTest.java new file mode 100644 index 00000000000..5e71c4abafb --- /dev/null +++ b/src/test/java/domain/BlackJackGameTest.java @@ -0,0 +1,132 @@ +//package domain; +// +//import static org.assertj.core.api.Assertions.assertThat; +//import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +//import static org.mockito.Mockito.atLeast; +//import static org.mockito.Mockito.times; +//import static org.mockito.Mockito.verify; +//import static org.mockito.Mockito.when; +// +//import dto.GameResultDto; +//import java.util.ArrayList; +//import java.util.HashMap; +//import java.util.LinkedHashMap; +//import java.util.List; +//import java.util.Map; +//import org.junit.jupiter.api.DisplayName; +//import org.junit.jupiter.api.Test; +//import org.junit.jupiter.api.extension.ExtendWith; +//import org.mockito.ArgumentCaptor; +//import org.mockito.Captor; +//import org.mockito.Mock; +//import org.mockito.junit.jupiter.MockitoExtension; +// +//@ExtendWith(MockitoExtension.class) +//class BlackJackGameTest { +// private static final List TEST_PLAYER_NAMES = List.of("pobi", "terry", "rati", "gump"); +// private static final CardCreationStrategy STRATEGY = BlackJackGameTest::createSampleCards; +// @Mock +// GameDelegate gameDelegate; +// @Captor +// ArgumentCaptor gameResultDtoArgumentCaptor; +// +// private static List createSampleCards() { +// CardShape[] shapes = CardShape.values(); +// CardContents[] contents = CardContents.values(); +// +// List sampleCards = new ArrayList<>(); +// for (CardShape cardShape : shapes) { +// for (CardContents content : contents) { +// sampleCards.add(new Card(cardShape, content)); +// } +// } +// +// return sampleCards; +// } +// +// @Test +// @DisplayName("생성 잘 한다") +// void ready_good() { +// //given +// +// //when, then +// assertDoesNotThrow( +// () -> BlackJackGame.ready(gameDelegate) +// ); +// } +// +// @Test +// @DisplayName("게임 플레이 잘함") +// void play_success() { +// //given +// when(gameDelegate.askPlayerNames()).thenReturn(TEST_PLAYER_NAMES); +// BlackJackGame blackJackGame = BlackJackGame.ready(gameDelegate); +// +// //when +// blackJackGame.play(gameDelegate); +// +// //then +// for (String name : TEST_PLAYER_NAMES) { +// verify(gameDelegate, atLeast(1)).askDrawCard(name); +// } +// verify(gameDelegate, atLeast(1)).showDealerOneMoreCardMessage(); +// +// } +// +// @Test +// @DisplayName("결과 계산 잘함") +// void calculate_success() { +// //given +// CardCreationStrategy strategy = new CardCreationStrategy() { +// @Override +// public List create() { +// return new ArrayList<>( +// List.of( +// new Card(CardShape.하트, CardContents.J), +// new Card(CardShape.하트, CardContents.EIGHT), +// new Card(CardShape.다이아몬드, CardContents.J), +// new Card(CardShape.다이아몬드, CardContents.THREE), +// new Card(CardShape.스페이드, CardContents.J), +// new Card(CardShape.스페이드, CardContents.THREE), +// new Card(CardShape.클로버, CardContents.J), +// new Card(CardShape.클로버, CardContents.NINE), +// new Card(CardShape.하트, CardContents.K), +// new Card(CardShape.하트, CardContents.NINE) +// ) +// ); +// } +// }; +// +// when(gameDelegate.askPlayerNames()).thenReturn(TEST_PLAYER_NAMES); +// BlackJackGame blackJackGame = BlackJackGame.ready(gameDelegate); +// Map expectDealerWinLossResults = consistExpectDealerWinLossResults(); +// Map expectPlayerWinLossResults = consistExpectPlayerWinLossResults(TEST_PLAYER_NAMES); +// +// //when +// blackJackGame.play(gameDelegate); +// blackJackGame.end(gameDelegate); +// +// //then +// verify(gameDelegate, times(1)) +// .showGameResult(gameResultDtoArgumentCaptor.capture()); +// GameResultDto result = gameResultDtoArgumentCaptor.getValue(); +// assertThat(result.dealerWinLossResults()).isEqualTo(expectDealerWinLossResults); +// assertThat(result.playerWinLossResults()).isEqualTo(expectPlayerWinLossResults); +// } +// +// private Map consistExpectPlayerWinLossResults(List testPlayerNames) { +// Map expectPlayerWinLossResults = new LinkedHashMap<>(); +// expectPlayerWinLossResults.put("pobi", "패"); +// expectPlayerWinLossResults.put("terry", "패"); +// expectPlayerWinLossResults.put("rati", "승"); +// expectPlayerWinLossResults.put("gump", "승"); +// return expectPlayerWinLossResults; +// } +// +// private Map consistExpectDealerWinLossResults() { +// Map expectDealerWinLossResults = new HashMap<>(); +// expectDealerWinLossResults.put("승", 2); +// expectDealerWinLossResults.put("패", 2); +// return expectDealerWinLossResults; +// } +//} \ No newline at end of file diff --git a/src/test/java/domain/GameTest.java b/src/test/java/domain/GameTest.java deleted file mode 100644 index 563be961163..00000000000 --- a/src/test/java/domain/GameTest.java +++ /dev/null @@ -1,133 +0,0 @@ -package domain; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.mockito.Mockito.atLeast; -import static org.mockito.Mockito.times; -import static org.mockito.Mockito.verify; -import static org.mockito.Mockito.when; - -import controller.GameDelegate; -import dto.GameResultDto; -import java.util.ArrayList; -import java.util.HashMap; -import java.util.LinkedHashMap; -import java.util.List; -import java.util.Map; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.junit.jupiter.api.extension.ExtendWith; -import org.mockito.ArgumentCaptor; -import org.mockito.Captor; -import org.mockito.Mock; -import org.mockito.junit.jupiter.MockitoExtension; - -@ExtendWith(MockitoExtension.class) -class GameTest { - private static final List TEST_PLAYER_NAMES = List.of("pobi", "terry", "rati", "gump"); - private static final CardCreationStrategy STRATEGY = GameTest::createSampleCards; - @Mock - GameDelegate gameDelegate; - @Captor - ArgumentCaptor gameResultDtoArgumentCaptor; - - private static List createSampleCards() { - CardShape[] shapes = CardShape.values(); - CardContents[] contents = CardContents.values(); - - List sampleCards = new ArrayList<>(); - for (CardShape cardShape : shapes) { - for (CardContents content : contents) { - sampleCards.add(new Card(cardShape, content)); - } - } - - return sampleCards; - } - - @Test - @DisplayName("생성 잘 한다") - void ready_good() { - //given - - //when, then - assertDoesNotThrow( - () -> Game.ready(gameDelegate, STRATEGY) - ); - } - - @Test - @DisplayName("게임 플레이 잘함") - void play_success() { - //given - when(gameDelegate.askPlayerNames()).thenReturn(TEST_PLAYER_NAMES); - Game game = Game.ready(gameDelegate, STRATEGY); - - //when - game.play(gameDelegate); - - //then - for (String name : TEST_PLAYER_NAMES) { - verify(gameDelegate, atLeast(1)).askDrawCard(name); - } - verify(gameDelegate, atLeast(1)).showDealerOneMoreCardMessage(); - - } - - @Test - @DisplayName("결과 계산 잘함") - void calculate_success() { - //given - CardCreationStrategy strategy = new CardCreationStrategy() { - @Override - public List create() { - return new ArrayList<>( - List.of( - new Card(CardShape.하트, CardContents.J), - new Card(CardShape.하트, CardContents.EIGHT), - new Card(CardShape.다이아몬드, CardContents.J), - new Card(CardShape.다이아몬드, CardContents.THREE), - new Card(CardShape.스페이드, CardContents.J), - new Card(CardShape.스페이드, CardContents.THREE), - new Card(CardShape.클로버, CardContents.J), - new Card(CardShape.클로버, CardContents.NINE), - new Card(CardShape.하트, CardContents.K), - new Card(CardShape.하트, CardContents.NINE) - ) - ); - } - }; - - when(gameDelegate.askPlayerNames()).thenReturn(TEST_PLAYER_NAMES); - Game game = Game.ready(gameDelegate, strategy); - Map expectDealerWinLossResults = consistExpectDealerWinLossResults(); - Map expectPlayerWinLossResults = consistExpectPlayerWinLossResults(TEST_PLAYER_NAMES); - - //when - game.play(gameDelegate); - game.end(gameDelegate); - - //then - verify(gameDelegate, times(1)) - .showGameResult(gameResultDtoArgumentCaptor.capture()); - GameResultDto result = gameResultDtoArgumentCaptor.getValue(); - assertThat(result.dealerWinLossResults()).isEqualTo(expectDealerWinLossResults); - assertThat(result.playerWinLossResults()).isEqualTo(expectPlayerWinLossResults); - } - - private Map consistExpectPlayerWinLossResults(List testPlayerNames) { - Map expectPlayerWinLossResults = new LinkedHashMap<>(); - expectPlayerWinLossResults.put("pobi", "패"); - expectPlayerWinLossResults.put("terry", "패"); - expectPlayerWinLossResults.put("rati", "승"); - expectPlayerWinLossResults.put("gump", "승"); - return expectPlayerWinLossResults; - } - - private Map consistExpectDealerWinLossResults() { - Map expectDealerWinLossResults = new HashMap<>(); - expectDealerWinLossResults.put("승", 2); - expectDealerWinLossResults.put("패", 2); - return expectDealerWinLossResults; - } -} \ No newline at end of file From 86dbfe052657cd5814ef36ca3029f15660be2836 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Thu, 12 Mar 2026 14:02:05 +0900 Subject: [PATCH 108/129] fix : delete delegate pattern of this proj MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit MVC 구조에서 delegate 없이도 구현이 가능하다고 판단 후 delegate 패턴이 과하다 판단하여 제거 --- src/main/java/controller/GameDelegate.java | 30 ---------------------- 1 file changed, 30 deletions(-) delete mode 100644 src/main/java/controller/GameDelegate.java diff --git a/src/main/java/controller/GameDelegate.java b/src/main/java/controller/GameDelegate.java deleted file mode 100644 index d9c94e45ea4..00000000000 --- a/src/main/java/controller/GameDelegate.java +++ /dev/null @@ -1,30 +0,0 @@ -package controller; - -import dto.GameResultDto; -import dto.ParticipantDto; -import java.util.List; - -public interface GameDelegate { - - /** - * 사용자의 입력을 요구. - */ - List askPlayerNames(); - - boolean askDrawCard(String playerName); - - /** - * 각종 정보 출력 - */ - // 초기 카드를 보여주기 - void showInitialParticipantCards(ParticipantDto dealerDto, List playerDtos); - - // 참가자 한명의 카드를 보여주기 - void showPlayerCards(ParticipantDto participantDto); - - // 딜러가 카드 한 장 더 받았음을 보여주기 - void showDealerOneMoreCardMessage(); - - // 게임의 결과를 보여주기 - void showGameResult(GameResultDto resultDto); -} From 6d412f5b867776fcaf856665025bc2a5475ca31e Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Thu, 12 Mar 2026 14:27:49 +0900 Subject: [PATCH 109/129] fix : change error message to clarify --- src/main/java/common/ErrorMessage.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/common/ErrorMessage.java b/src/main/java/common/ErrorMessage.java index a81f268e314..ce52691ac52 100644 --- a/src/main/java/common/ErrorMessage.java +++ b/src/main/java/common/ErrorMessage.java @@ -2,7 +2,7 @@ public enum ErrorMessage { HIT_OR_STAND_VALUE_MIS_MATCH("y 혹은 n만 입력 가능합니다."), - DRAW_CARD_OUT_OF_RANGE("양수 이상의 숫자 중 남은 카드 수 만큼만 선택 가능"), + DRAW_CARD_OUT_OF_RANGE("남은 카드 수 만큼만 선택 가능"), UNSUPPORTED_OPERATION_MESSAGE("해당 객체[%s]에서는 지원하지 않는 메서드입니다 "), NOT_ALLOW_EMPTY_INPUT("공백은 허용되지 않습니다"), ONLY_KO_AND_ENG("이름은 영어 또는 한국어만 가능합니다: "), From 116ba65a237adee860676ebec293d129d998280a Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Thu, 12 Mar 2026 14:43:08 +0900 Subject: [PATCH 110/129] fix : refactor game logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 기존의 흐름을 통제하는 로직이 변경. Game은 내부 도메인들 사이를 중재하는 역할을 담당하게 되었음. - 이에 따라 game의 이름 및 로직이 대폭 변경됨에 따라 변경 --- src/test/java/domain/BlackJackGameTest.java | 304 +++++++++++--------- 1 file changed, 172 insertions(+), 132 deletions(-) diff --git a/src/test/java/domain/BlackJackGameTest.java b/src/test/java/domain/BlackJackGameTest.java index 5e71c4abafb..604980487d7 100644 --- a/src/test/java/domain/BlackJackGameTest.java +++ b/src/test/java/domain/BlackJackGameTest.java @@ -1,132 +1,172 @@ -//package domain; -// -//import static org.assertj.core.api.Assertions.assertThat; -//import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -//import static org.mockito.Mockito.atLeast; -//import static org.mockito.Mockito.times; -//import static org.mockito.Mockito.verify; -//import static org.mockito.Mockito.when; -// -//import dto.GameResultDto; -//import java.util.ArrayList; -//import java.util.HashMap; -//import java.util.LinkedHashMap; -//import java.util.List; -//import java.util.Map; -//import org.junit.jupiter.api.DisplayName; -//import org.junit.jupiter.api.Test; -//import org.junit.jupiter.api.extension.ExtendWith; -//import org.mockito.ArgumentCaptor; -//import org.mockito.Captor; -//import org.mockito.Mock; -//import org.mockito.junit.jupiter.MockitoExtension; -// -//@ExtendWith(MockitoExtension.class) -//class BlackJackGameTest { -// private static final List TEST_PLAYER_NAMES = List.of("pobi", "terry", "rati", "gump"); -// private static final CardCreationStrategy STRATEGY = BlackJackGameTest::createSampleCards; -// @Mock -// GameDelegate gameDelegate; -// @Captor -// ArgumentCaptor gameResultDtoArgumentCaptor; -// -// private static List createSampleCards() { -// CardShape[] shapes = CardShape.values(); -// CardContents[] contents = CardContents.values(); -// -// List sampleCards = new ArrayList<>(); -// for (CardShape cardShape : shapes) { -// for (CardContents content : contents) { -// sampleCards.add(new Card(cardShape, content)); -// } -// } -// -// return sampleCards; -// } -// -// @Test -// @DisplayName("생성 잘 한다") -// void ready_good() { -// //given -// -// //when, then -// assertDoesNotThrow( -// () -> BlackJackGame.ready(gameDelegate) -// ); -// } -// -// @Test -// @DisplayName("게임 플레이 잘함") -// void play_success() { -// //given -// when(gameDelegate.askPlayerNames()).thenReturn(TEST_PLAYER_NAMES); -// BlackJackGame blackJackGame = BlackJackGame.ready(gameDelegate); -// -// //when -// blackJackGame.play(gameDelegate); -// -// //then -// for (String name : TEST_PLAYER_NAMES) { -// verify(gameDelegate, atLeast(1)).askDrawCard(name); -// } -// verify(gameDelegate, atLeast(1)).showDealerOneMoreCardMessage(); -// -// } -// -// @Test -// @DisplayName("결과 계산 잘함") -// void calculate_success() { -// //given -// CardCreationStrategy strategy = new CardCreationStrategy() { -// @Override -// public List create() { -// return new ArrayList<>( -// List.of( -// new Card(CardShape.하트, CardContents.J), -// new Card(CardShape.하트, CardContents.EIGHT), -// new Card(CardShape.다이아몬드, CardContents.J), -// new Card(CardShape.다이아몬드, CardContents.THREE), -// new Card(CardShape.스페이드, CardContents.J), -// new Card(CardShape.스페이드, CardContents.THREE), -// new Card(CardShape.클로버, CardContents.J), -// new Card(CardShape.클로버, CardContents.NINE), -// new Card(CardShape.하트, CardContents.K), -// new Card(CardShape.하트, CardContents.NINE) -// ) -// ); -// } -// }; -// -// when(gameDelegate.askPlayerNames()).thenReturn(TEST_PLAYER_NAMES); -// BlackJackGame blackJackGame = BlackJackGame.ready(gameDelegate); -// Map expectDealerWinLossResults = consistExpectDealerWinLossResults(); -// Map expectPlayerWinLossResults = consistExpectPlayerWinLossResults(TEST_PLAYER_NAMES); -// -// //when -// blackJackGame.play(gameDelegate); -// blackJackGame.end(gameDelegate); -// -// //then -// verify(gameDelegate, times(1)) -// .showGameResult(gameResultDtoArgumentCaptor.capture()); -// GameResultDto result = gameResultDtoArgumentCaptor.getValue(); -// assertThat(result.dealerWinLossResults()).isEqualTo(expectDealerWinLossResults); -// assertThat(result.playerWinLossResults()).isEqualTo(expectPlayerWinLossResults); -// } -// -// private Map consistExpectPlayerWinLossResults(List testPlayerNames) { -// Map expectPlayerWinLossResults = new LinkedHashMap<>(); -// expectPlayerWinLossResults.put("pobi", "패"); -// expectPlayerWinLossResults.put("terry", "패"); -// expectPlayerWinLossResults.put("rati", "승"); -// expectPlayerWinLossResults.put("gump", "승"); -// return expectPlayerWinLossResults; -// } -// -// private Map consistExpectDealerWinLossResults() { -// Map expectDealerWinLossResults = new HashMap<>(); -// expectDealerWinLossResults.put("승", 2); -// expectDealerWinLossResults.put("패", 2); -// return expectDealerWinLossResults; -// } -//} \ No newline at end of file +package domain; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import dto.GameResultDto; +import dto.ParticipantDto; +import java.util.ArrayDeque; +import java.util.ArrayList; +import java.util.Deque; +import java.util.List; +import java.util.Optional; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +class BlackJackGameTest { + private static final List TEST_PLAYER_NAMES = List.of("pobi", "terry", "rati", "gump"); + + @Test + @DisplayName("생성 잘 한다") + void ready_good() { + //given + Deck sampleDeck = Deck.createDeck(this::createSampleCards); + + //when, then + assertDoesNotThrow( + () -> BlackJackGame.ready(TEST_PLAYER_NAMES, this::createSampleCards) + ); + } + + @Test + @DisplayName("다음 턴의 사용자가 있으면 제공한다") + void whoseTure_success() { + BlackJackGame testGame = BlackJackGame.ready(TEST_PLAYER_NAMES, this::createSampleCards); + + Optional result = testGame.whoseTurn(); + + assertTrue(result.isPresent()); + } + + @Test + @DisplayName("hit 프로세스를 적합한 턴의 플레이어에게 요청하고 결과로 ParticipantDto를 반환받는다") + void doHitProcess_success() { + BlackJackGame testGame = BlackJackGame.ready(TEST_PLAYER_NAMES, this::createSampleCards); + + ParticipantDto result = testGame.doHitProcess(); + + assertEquals(ParticipantDto.class, result.getClass()); + } + + @Test + @DisplayName("stand 프로세스를 적합한 턴의 플레이어에게 요청하고 결과로 ParticipantDto를 반환받는다") + void doStandProcess_success() { + BlackJackGame testGame = BlackJackGame.ready(TEST_PLAYER_NAMES, this::createSampleCards); + + ParticipantDto result = testGame.doStandProcess(); + + assertEquals(ParticipantDto.class, result.getClass()); + } + + @Nested + class doDealerHitOrStandProcessTest { + + List playersCardsCandidates = List.of( + new Card(CardShape.하트, CardContents.FOUR), + new Card(CardShape.하트, CardContents.FIVE), + new Card(CardShape.하트, CardContents.SIX), + new Card(CardShape.하트, CardContents.SEVEN), + new Card(CardShape.하트, CardContents.EIGHT), + new Card(CardShape.하트, CardContents.NINE), + new Card(CardShape.하트, CardContents.TEN), + new Card(CardShape.하트, CardContents.J), + new Card(CardShape.하트, CardContents.Q), + new Card(CardShape.하트, CardContents.K) + ); + + @Test + @DisplayName("카드를 추가하면 true 반환") + void doDealerHitOrStandProcess_return_true() { + //given + List testCards = new ArrayList<>(); + List dealersCardsThatUnderThan16 = List.of( + new Card(CardShape.하트, CardContents.TWO), + new Card(CardShape.하트, CardContents.THREE) + ); + + testCards.addAll(dealersCardsThatUnderThan16); + testCards.addAll(playersCardsCandidates); + + Deque testDeckCards = new ArrayDeque<>(testCards); + BlackJackGame testGame = BlackJackGame.ready(TEST_PLAYER_NAMES, () -> testDeckCards); + + //when + boolean result = testGame.doDealerHitOrStandProcess(); + + //then + assertTrue(result); + } + + @Test + @DisplayName("카드를 추가안하면 false 반환") + void doDealerHitOrStandProcess_return_false() { + //given + List testCards = new ArrayList<>(); + List dealersCardsThatOverThan16 = List.of( + new Card(CardShape.하트, CardContents.TEN), + new Card(CardShape.하트, CardContents.Q) + ); + testCards.addAll(dealersCardsThatOverThan16); + testCards.addAll(playersCardsCandidates); + + Deque testDeckCards = new ArrayDeque<>(testCards); + BlackJackGame testGame = BlackJackGame.ready(TEST_PLAYER_NAMES, () -> testDeckCards); + + //when + boolean result = testGame.doDealerHitOrStandProcess(); + + //then + assertFalse(result); + } + } + + @Test + @DisplayName("플레이어들의 게임 초기 상태를 인원 수 대로 잘 가져온다") + void getPlayersGameSettingStates_good() { + BlackJackGame testGame = BlackJackGame.ready(TEST_PLAYER_NAMES, this::createSampleCards); + + int expectSize = TEST_PLAYER_NAMES.size(); + int resultSize = testGame.getPlayersGameSettingStates().size(); + assertEquals(expectSize, resultSize); + } + + @Test + @DisplayName("딜러의 게임 초기 상태를 ParticipantDto 형태로 잘 받는다") + void getDealerGameSettingState_good() { + BlackJackGame testGame = BlackJackGame.ready(TEST_PLAYER_NAMES, this::createSampleCards); + + assertEquals(ParticipantDto.class, testGame.getDealerGameSettingState().getClass()); + } + + @Test + @DisplayName("플레이어들과 사용자의 정보를 잘 받아와서 GameResultDto를 반환한다") + void getGameResults_good() { + BlackJackGame testGame = BlackJackGame.ready(TEST_PLAYER_NAMES, this::createSampleCards); + + GameResultDto result = testGame.getGameResults(); + + int expectPlayerResultDtoSize = TEST_PLAYER_NAMES.size(); + int resultSize = result.playerResultDto().size(); + + assertEquals(expectPlayerResultDtoSize, resultSize); + assertNotNull(result.dealerResultDto()); + } + + private Deque createSampleCards() { + CardShape[] shapes = CardShape.values(); + CardContents[] contents = CardContents.values(); + + List sampleCards = new ArrayList<>(); + for (CardShape cardShape : shapes) { + for (CardContents content : contents) { + sampleCards.add(new Card(cardShape, content)); + } + } + + return new ArrayDeque<>(sampleCards); + } +} \ No newline at end of file From 16181df97d2627f3be80d29792e4255bcb356ed4 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Thu, 12 Mar 2026 15:00:26 +0900 Subject: [PATCH 111/129] fix : change controller logic MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 전체 흐름을 주관하는 책임을 다시 부여 - 이에 따른 테스트 로직 변경 - 테스트에서 mock 제거. 최대한 순수 자바를 사용할 수 있도록 변경 --- src/main/java/Main.java | 10 +- .../java/controller/BlackJackController.java | 17 +- src/main/java/view/InputView.java | 34 +-- src/main/java/view/InputViewImpl.java | 37 +++ src/main/java/view/OutputView.java | 108 +------- src/main/java/view/OutputViewImpl.java | 113 +++++++++ .../controller/BlackJackControllerTest.java | 232 +++++++++--------- 7 files changed, 293 insertions(+), 258 deletions(-) create mode 100644 src/main/java/view/InputViewImpl.java create mode 100644 src/main/java/view/OutputViewImpl.java diff --git a/src/main/java/Main.java b/src/main/java/Main.java index 99b27e98929..0376cb0f51a 100644 --- a/src/main/java/Main.java +++ b/src/main/java/Main.java @@ -1,15 +1,15 @@ import controller.BlackJackController; import domain.CardCreationStrategy; import domain.RandomCardCreationStrategy; -import view.InputView; -import view.OutputView; +import view.InputViewImpl; +import view.OutputViewImpl; public class Main { public static void main(String[] args) { - InputView inputView = new InputView(); - OutputView outputView = new OutputView(); + InputViewImpl inputViewImpl = new InputViewImpl(); + OutputViewImpl outputViewImpl = new OutputViewImpl(); CardCreationStrategy strategy = new RandomCardCreationStrategy(); - new BlackJackController(inputView, outputView, strategy).doGameProcess(); + new BlackJackController(inputViewImpl, outputViewImpl, strategy).doGameProcess(); } } diff --git a/src/main/java/controller/BlackJackController.java b/src/main/java/controller/BlackJackController.java index 404467f4c45..f86a7d3a7ee 100644 --- a/src/main/java/controller/BlackJackController.java +++ b/src/main/java/controller/BlackJackController.java @@ -3,6 +3,7 @@ import domain.BlackJackGame; import domain.CardCreationStrategy; import domain.Player; +import dto.GameResultDto; import dto.ParticipantDto; import java.util.List; import java.util.function.Supplier; @@ -15,7 +16,9 @@ public class BlackJackController { private final OutputView outputView; private final CardCreationStrategy strategy; - public BlackJackController(InputView inputView, OutputView outputView, CardCreationStrategy strategy) { + public BlackJackController(InputView inputView, + OutputView outputView, + CardCreationStrategy strategy) { this.inputView = inputView; this.outputView = outputView; this.strategy = strategy; @@ -30,6 +33,9 @@ public void doGameProcess() { playPlayersTurn(game); playDealerTurn(game); + + GameResultDto gameResults = game.getGameResults(); + outputView.printGameResult(gameResults); } private BlackJackGame readyGame() { @@ -48,6 +54,11 @@ private void playPlayersTurn(BlackJackGame game) { while (game.whoseTurn().isPresent()) { Player currentPlayer = game.whoseTurn().get(); + if (currentPlayer.isFinished()) { + handlePlayerStandProcess(game); + continue; + } + outputView.printHitOrStandPrompt(currentPlayer.getName()); String hitOrStandInfo = retry(inputView::readHitOrStand); doHitOrStand(hitOrStandInfo, game); @@ -57,9 +68,9 @@ private void playPlayersTurn(BlackJackGame game) { private void doHitOrStand(String hitOrStand, BlackJackGame game) { if (hitOrStand.equals("y")) { handlePlayerHitProcess(game); - return; + } else { + handlePlayerStandProcess(game); } - handlePlayerStandProcess(game); } private void handlePlayerStandProcess(BlackJackGame game) { diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 0c6d743846b..d482849d6d3 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -1,37 +1,9 @@ package view; -import common.ErrorMessage; -import java.util.Arrays; import java.util.List; -import java.util.Scanner; -public class InputView { - private static final String DELIMITER = ","; - private static final List HIT_OR_STAND = List.of("y", "n"); - private static final Scanner sc = new Scanner(System.in); +public interface InputView { + List readNames(); - public List readNames() { - String line = sc.nextLine(); - validateIsBlank(line); - return Arrays.stream(line.split(DELIMITER)).toList(); - } - - public String readHitOrStand() { - String input = sc.nextLine().trim(); - validateIsBlank(input); - validateHitOrStandValue(input); - return input; - } - - private void validateIsBlank(String line) { - if (line.isBlank()) { - throw new IllegalArgumentException(ErrorMessage.NOT_ALLOW_EMPTY_INPUT.getMessage()); - } - } - - private void validateHitOrStandValue(String input) { - if (!HIT_OR_STAND.contains(input)) { - throw new IllegalArgumentException(ErrorMessage.HIT_OR_STAND_VALUE_MIS_MATCH.getMessage()); - } - } + String readHitOrStand(); } diff --git a/src/main/java/view/InputViewImpl.java b/src/main/java/view/InputViewImpl.java new file mode 100644 index 00000000000..f66b3d6ac00 --- /dev/null +++ b/src/main/java/view/InputViewImpl.java @@ -0,0 +1,37 @@ +package view; + +import common.ErrorMessage; +import java.util.Arrays; +import java.util.List; +import java.util.Scanner; + +public class InputViewImpl implements InputView { + private static final String DELIMITER = ","; + private static final List HIT_OR_STAND = List.of("y", "n"); + private static final Scanner sc = new Scanner(System.in); + + public List readNames() { + String line = sc.nextLine(); + validateIsBlank(line); + return Arrays.stream(line.split(DELIMITER)).toList(); + } + + public String readHitOrStand() { + String input = sc.nextLine().trim(); + validateIsBlank(input); + validateHitOrStandValue(input); + return input; + } + + private void validateIsBlank(String line) { + if (line.isBlank()) { + throw new IllegalArgumentException(ErrorMessage.NOT_ALLOW_EMPTY_INPUT.getMessage()); + } + } + + private void validateHitOrStandValue(String input) { + if (!HIT_OR_STAND.contains(input)) { + throw new IllegalArgumentException(ErrorMessage.HIT_OR_STAND_VALUE_MIS_MATCH.getMessage()); + } + } +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index db41eea33a5..49a5d4b41b9 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,113 +1,21 @@ package view; -import domain.GameResult; -import dto.CardDto; -import dto.DealerResultDto; import dto.GameResultDto; import dto.ParticipantDto; -import dto.PlayerResultDto; import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -public class OutputView { - private static final String DELIMITER = ", "; - private static final String NAME_PROMPT = "게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)"; - private static final String INITIAL_CARD_SHARE = "\n딜러와 %s에게 2장을 나누었습니다.\n"; - private static final String HIT_OR_STAND_PROMPT = "%s는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)\n"; - private static final String DEALER_ADD_CARD_NOTICE = "\n딜러는 16이하라 한장의 카드를 더 받았습니다.\n"; - private static final String PARTICIPANT_CARD_INFO_FORMAT = "%s카드: %s"; - private static final String WIN_LOSS_RESULT_HEADER = "\n## 최종 승패"; - private static final String PARTICIPANT_CARD_INFO_WITH_SUM_FORMAT = "%s - 결과: %d\n"; - private static final String WIN_LOSS_RESULT_FORMAT = "%s: %s\n"; +public interface OutputView { + void printErrorMessage(Exception e); - public void printErrorMessage(Exception e) { - System.out.println(e.getMessage()); - } + void printNamePrompt(); - public void printNamePrompt() { - System.out.println(NAME_PROMPT); - } + void printInitialStates(ParticipantDto dealerDto, List players); - public void printInitialStates(ParticipantDto dealerDto, List players) { - String playerNames = players.stream() - .map(ParticipantDto::name) - .collect(Collectors.joining(DELIMITER)); + void printHitOrStandPrompt(String name); - System.out.printf(INITIAL_CARD_SHARE, playerNames); - - printUserState(dealerDto); - players.forEach(this::printUserState); - - System.out.println(); - } + void printUserState(ParticipantDto participantDto); - public void printHitOrStandPrompt(String name) { - System.out.printf(HIT_OR_STAND_PROMPT, name); - } + void printDealerAddCardNotice(); - public void printUserState(ParticipantDto participantDto) { - System.out.println(formatParticipantCards(participantDto)); - } - - public void printDealerAddCardNotice() { - System.out.println(DEALER_ADD_CARD_NOTICE); - } - - public void printGameResult(GameResultDto gameResultDto) { - printFinalStates(gameResultDto); - printFinalWinLoss(gameResultDto); - } - - private void printFinalStates(GameResultDto gameResultDto) { - DealerResultDto dealerResultDto = gameResultDto.dealerResultDto(); - printParticipantFinalState(dealerResultDto.dealerDto(), dealerResultDto.score()); - - List playerResultDtos = gameResultDto.playerResultDto(); - playerResultDtos.forEach(dto -> printParticipantFinalState(dto.playerDto(), dto.score())); - } - - private void printParticipantFinalState(ParticipantDto participantDto, int score) { - System.out.printf(PARTICIPANT_CARD_INFO_WITH_SUM_FORMAT, formatParticipantCards(participantDto), score); - } - - private void printFinalWinLoss(GameResultDto gameResultDto) { - System.out.println(WIN_LOSS_RESULT_HEADER); - - DealerResultDto dealerResultDto = gameResultDto.dealerResultDto(); - System.out.print(formatDealerResult(dealerResultDto)); - - List playerResultDtos = gameResultDto.playerResultDto(); - playerResultDtos.forEach(dto -> - System.out.printf(WIN_LOSS_RESULT_FORMAT, dto.playerDto().name(), dto.result().name()) - ); - } - - private String formatParticipantCards(ParticipantDto participantDto) { - List ownCards = participantDto.cards(); - return String.format(PARTICIPANT_CARD_INFO_FORMAT, participantDto.name(), formatCardsInfo(ownCards)); - } - - private String formatCardsInfo(List cards) { - return cards.stream() - .map(card -> card.cardContentNumber() + card.cardShape()) - .collect(Collectors.joining(DELIMITER)); - } - - private String formatDealerResult(DealerResultDto dealerResultDto) { - String dealerName = dealerResultDto.dealerDto().name(); - String resultString = formatDealerResultInfo(dealerResultDto.dealerResult()); - return String.format(WIN_LOSS_RESULT_FORMAT, dealerName, resultString); - } - - private String formatDealerResultInfo(Map dealerGameResult) { - StringBuilder sb = new StringBuilder(); - for (GameResult result : GameResult.values()) { - int count = dealerGameResult.getOrDefault(result, 0); - if (count > 0) { - sb.append(count).append(result.name()).append(" "); - } - } - return sb.toString().trim(); - } + void printGameResult(GameResultDto gameResultDto); } diff --git a/src/main/java/view/OutputViewImpl.java b/src/main/java/view/OutputViewImpl.java new file mode 100644 index 00000000000..19c2ea81048 --- /dev/null +++ b/src/main/java/view/OutputViewImpl.java @@ -0,0 +1,113 @@ +package view; + +import domain.GameResult; +import dto.CardDto; +import dto.DealerResultDto; +import dto.GameResultDto; +import dto.ParticipantDto; +import dto.PlayerResultDto; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +public class OutputViewImpl implements OutputView { + private static final String DELIMITER = ", "; + private static final String NAME_PROMPT = "게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)"; + private static final String INITIAL_CARD_SHARE = "\n딜러와 %s에게 2장을 나누었습니다.\n"; + private static final String HIT_OR_STAND_PROMPT = "%s는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)\n"; + private static final String DEALER_ADD_CARD_NOTICE = "\n딜러는 16이하라 한장의 카드를 더 받았습니다.\n"; + private static final String PARTICIPANT_CARD_INFO_FORMAT = "%s카드: %s"; + private static final String WIN_LOSS_RESULT_HEADER = "\n## 최종 승패"; + private static final String PARTICIPANT_CARD_INFO_WITH_SUM_FORMAT = "%s - 결과: %d\n"; + private static final String WIN_LOSS_RESULT_FORMAT = "%s: %s\n"; + + public void printErrorMessage(Exception e) { + System.out.println(e.getMessage()); + } + + public void printNamePrompt() { + System.out.println(NAME_PROMPT); + } + + public void printInitialStates(ParticipantDto dealerDto, List players) { + String playerNames = players.stream() + .map(ParticipantDto::name) + .collect(Collectors.joining(DELIMITER)); + + System.out.printf(INITIAL_CARD_SHARE, playerNames); + + printUserState(dealerDto); + players.forEach(this::printUserState); + + System.out.println(); + } + + public void printHitOrStandPrompt(String name) { + System.out.printf(HIT_OR_STAND_PROMPT, name); + } + + public void printUserState(ParticipantDto participantDto) { + System.out.println(formatParticipantCards(participantDto)); + } + + public void printDealerAddCardNotice() { + System.out.println(DEALER_ADD_CARD_NOTICE); + } + + public void printGameResult(GameResultDto gameResultDto) { + printFinalStates(gameResultDto); + printFinalWinLoss(gameResultDto); + } + + private void printFinalStates(GameResultDto gameResultDto) { + DealerResultDto dealerResultDto = gameResultDto.dealerResultDto(); + printParticipantFinalState(dealerResultDto.dealerDto(), dealerResultDto.score()); + + List playerResultDtos = gameResultDto.playerResultDto(); + playerResultDtos.forEach(dto -> printParticipantFinalState(dto.playerDto(), dto.score())); + } + + private void printParticipantFinalState(ParticipantDto participantDto, int score) { + System.out.printf(PARTICIPANT_CARD_INFO_WITH_SUM_FORMAT, formatParticipantCards(participantDto), score); + } + + private void printFinalWinLoss(GameResultDto gameResultDto) { + System.out.println(WIN_LOSS_RESULT_HEADER); + + DealerResultDto dealerResultDto = gameResultDto.dealerResultDto(); + System.out.print(formatDealerResult(dealerResultDto)); + + List playerResultDtos = gameResultDto.playerResultDto(); + playerResultDtos.forEach(dto -> + System.out.printf(WIN_LOSS_RESULT_FORMAT, dto.playerDto().name(), dto.result().name()) + ); + } + + private String formatParticipantCards(ParticipantDto participantDto) { + List ownCards = participantDto.cards(); + return String.format(PARTICIPANT_CARD_INFO_FORMAT, participantDto.name(), formatCardsInfo(ownCards)); + } + + private String formatCardsInfo(List cards) { + return cards.stream() + .map(card -> card.cardContentNumber() + card.cardShape()) + .collect(Collectors.joining(DELIMITER)); + } + + private String formatDealerResult(DealerResultDto dealerResultDto) { + String dealerName = dealerResultDto.dealerDto().name(); + String resultString = formatDealerResultInfo(dealerResultDto.dealerResult()); + return String.format(WIN_LOSS_RESULT_FORMAT, dealerName, resultString); + } + + private String formatDealerResultInfo(Map dealerGameResult) { + StringBuilder sb = new StringBuilder(); + for (GameResult result : GameResult.values()) { + int count = dealerGameResult.getOrDefault(result, 0); + if (count > 0) { + sb.append(count).append(result.name()).append(" "); + } + } + return sb.toString().trim(); + } +} diff --git a/src/test/java/controller/BlackJackControllerTest.java b/src/test/java/controller/BlackJackControllerTest.java index 848cf65cca3..a48c5627bea 100644 --- a/src/test/java/controller/BlackJackControllerTest.java +++ b/src/test/java/controller/BlackJackControllerTest.java @@ -1,119 +1,113 @@ -//package controller; -// -//import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -//import static org.mockito.Mockito.times; -//import static org.mockito.Mockito.verify; -//import static org.mockito.Mockito.when; -// -//import domain.Card; -//import domain.CardContents; -//import domain.CardCreationStrategy; -//import domain.CardShape; -//import domain.Deck; -//import domain.Player; -//import dto.ParticipantDto; -//import java.util.ArrayList; -//import java.util.List; -//import org.assertj.core.api.Assertions; -//import org.junit.jupiter.api.DisplayName; -//import org.junit.jupiter.api.Nested; -//import org.junit.jupiter.api.Test; -//import org.junit.jupiter.api.extension.ExtendWith; -//import org.mockito.InjectMocks; -//import org.mockito.Mock; -//import org.mockito.junit.jupiter.MockitoExtension; -//import view.InputView; -//import view.OutputView; -// -//@ExtendWith(MockitoExtension.class) -//class BlackJackControllerTest { -// private static final String TEST_NAME = "tester"; -// -// @Mock -// private InputView inputView; -// -// @Mock -// private OutputView outputView; -// -// @Mock -// private CardCreationStrategy strategy; -// -// @InjectMocks -// private BlackJackController controller; -// -// @Test -// @DisplayName("이름 잘 물어봄") -// void askPlayerNames_success() { -// //given -// when(inputView.readNames()).thenReturn(List.of("pobi", "gump")); -// -// //when -// List result = controller.askPlayerNames(); -// -// //then -// verify(outputView, times(1)).printNamePrompt(); -// verify(inputView, times(1)).readNames(); -// } -// -// @Test -// @DisplayName("카드 내용 출력 전달 잘함") -// void showPlayerCards_success() { -// //given -// List testCards = List.of( -// new Card(CardShape.하트, CardContents.TWO), -// new Card(CardShape.하트, CardContents.THREE), -// new Card(CardShape.하트, CardContents.FOUR) -// ); -// CardCreationStrategy strategy = new CardCreationStrategy() { -// @Override -// public List create() { -// return new ArrayList<>(testCards); -// } -// }; -// -// Deck totalDeck = Deck.createDeck(strategy); -// Deck playerDeck = Deck.createParticipantDeck(totalDeck); -// Player PLAYER = new Player(playerDeck, TEST_NAME); -// ParticipantDto PARTICIPANT_DTO = ParticipantDto.from(PLAYER); -// -// //when && then -// assertDoesNotThrow( -// () -> controller.showPlayerCards(PARTICIPANT_DTO) -// ); -// } -// -// @Nested -// class addDrawCardTest { -// @Test -// @DisplayName("질문해서 y 면 true 반환") -// void askDrawCard_true() { -// //given -// when(inputView.readHitOrStand()).thenReturn("y"); -// -// //when -// boolean result = controller.askDrawCard(TEST_NAME); -// -// //then -// Assertions.assertThat(result).isTrue(); -// } -// -// @Test -// @DisplayName("질문해서 n이면 false 반환") -// void askDrawCard_false() { -// //given -// when(inputView.readHitOrStand()).thenReturn("n"); -// -// //when -// boolean result = controller.askDrawCard(TEST_NAME); -// -// //then -// Assertions.assertThat(result).isFalse(); -// } -// } -// -// //private List createSampleCards() { -/// / CardShape[] shapes = CardShape.values(); / CardContents[] contents = CardContents.values(); / / -/// List sampleCards = new ArrayList<>(); / for (CardShape cardShape : shapes) { / for -/// (CardContents content : contents) { / sampleCards.add(new Card(cardShape, content)); / } / -/// } / / return sampleCards; / } -//} +package controller; + +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; + +import domain.Card; +import domain.CardContents; +import domain.CardCreationStrategy; +import domain.CardShape; +import dto.GameResultDto; +import dto.ParticipantDto; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.List; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import view.InputView; +import view.OutputView; + +class BlackJackControllerTest { + private static final String TEST_NAME = "tester"; + + private BlackJackController controller; + + @BeforeEach + void setUpGame() { + controller = new BlackJackController( + new TestInputViewImpl(), + new TestOutputViewImpl(), + new TestCardCreationStrategy() + ); + } + + @Test + @DisplayName("게임이 정상적으로 수행되면 오류가 발생하지 않는다") + void askPlayerNames_success() { + //when, then + assertDoesNotThrow( + () -> controller.doGameProcess() + ); + } + + class TestInputViewImpl implements InputView { + + private static final Deque hitOrStandOrder = new ArrayDeque<>(List.of("y", "n", "n", "n")); + + @Override + public List readNames() { + return List.of("pobi", "jason", "gump"); + } + + @Override + public String readHitOrStand() { + return hitOrStandOrder.poll(); + } + } + + class TestOutputViewImpl implements OutputView { + + @Override + public void printErrorMessage(Exception e) { + } + + @Override + public void printNamePrompt() { + } + + @Override + public void printInitialStates(ParticipantDto dealerDto, List players) { + } + + @Override + public void printHitOrStandPrompt(String name) { + } + + @Override + public void printUserState(ParticipantDto participantDto) { + } + + @Override + public void printDealerAddCardNotice() { + } + + @Override + public void printGameResult(GameResultDto gameResultDto) { + } + } + + class TestCardCreationStrategy implements CardCreationStrategy { + + @Override + public Deque create() { + List candidateCard = List.of( + new Card(CardShape.하트, CardContents.A), + new Card(CardShape.하트, CardContents.TWO), + new Card(CardShape.하트, CardContents.THREE), + new Card(CardShape.하트, CardContents.FOUR), + new Card(CardShape.하트, CardContents.FIVE), + new Card(CardShape.하트, CardContents.SIX), + new Card(CardShape.하트, CardContents.SEVEN), + new Card(CardShape.하트, CardContents.EIGHT), + new Card(CardShape.하트, CardContents.NINE), + new Card(CardShape.하트, CardContents.TEN), + new Card(CardShape.하트, CardContents.J), + new Card(CardShape.하트, CardContents.Q), + new Card(CardShape.하트, CardContents.K) + ); + + return new ArrayDeque<>(candidateCard); + } + } + +} From 1831d8e80b9eb23b8499ca7d3749ac0cf9f78cf0 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Thu, 12 Mar 2026 15:20:36 +0900 Subject: [PATCH 112/129] fix : fix controller test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 역할이 분리됨에 따라 테스트 로직 변경 --- src/test/java/controller/BlackJackControllerTest.java | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/test/java/controller/BlackJackControllerTest.java b/src/test/java/controller/BlackJackControllerTest.java index a48c5627bea..52fc1485e8d 100644 --- a/src/test/java/controller/BlackJackControllerTest.java +++ b/src/test/java/controller/BlackJackControllerTest.java @@ -18,8 +18,6 @@ import view.OutputView; class BlackJackControllerTest { - private static final String TEST_NAME = "tester"; - private BlackJackController controller; @BeforeEach @@ -40,7 +38,7 @@ void askPlayerNames_success() { ); } - class TestInputViewImpl implements InputView { + static class TestInputViewImpl implements InputView { private static final Deque hitOrStandOrder = new ArrayDeque<>(List.of("y", "n", "n", "n")); @@ -55,7 +53,7 @@ public String readHitOrStand() { } } - class TestOutputViewImpl implements OutputView { + static class TestOutputViewImpl implements OutputView { @Override public void printErrorMessage(Exception e) { @@ -86,7 +84,7 @@ public void printGameResult(GameResultDto gameResultDto) { } } - class TestCardCreationStrategy implements CardCreationStrategy { + static class TestCardCreationStrategy implements CardCreationStrategy { @Override public Deque create() { From 714eb80040dba7a64ed1e33ddd8933bbff2465ca Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Thu, 12 Mar 2026 15:57:02 +0900 Subject: [PATCH 113/129] fix : delete unused logic --- src/main/java/dto/GameResultDto.java | 24 ------------------------ src/main/java/dto/PlayerDto.java | 9 --------- 2 files changed, 33 deletions(-) delete mode 100644 src/main/java/dto/PlayerDto.java diff --git a/src/main/java/dto/GameResultDto.java b/src/main/java/dto/GameResultDto.java index 86ddc9694cb..6da9bd4949e 100644 --- a/src/main/java/dto/GameResultDto.java +++ b/src/main/java/dto/GameResultDto.java @@ -3,28 +3,4 @@ import java.util.List; public record GameResultDto(DealerResultDto dealerResultDto, List playerResultDto) { - -// public static GameResultDto from(Dealer dealer, Players players, -// Map dealerWinLossResults, -// Map playerWinLossResults) { -// -// ParticipantDto dealerDto = ParticipantDto.from(dealer); -// -// List playerDtos = new ArrayList<>(); -// for (Player player : players.getPlayers()) { -// playerDtos.add(ParticipantDto.from(player)); -// } -// -// Map dealerResults = new LinkedHashMap<>(); -// for (Entry entry : dealerWinLossResults.entrySet()) { -// dealerResults.put(entry.getKey().name(), entry.getValue()); -// } -// -// Map playerResults = new LinkedHashMap<>(); -// for (Entry entry : playerWinLossResults.entrySet()) { -// playerResults.put(entry.getKey().getName(), entry.getValue().name()); -// } -// -// return new GameResultDto(dealerDto, playerDtos, dealerResults, playerResults); -// } } diff --git a/src/main/java/dto/PlayerDto.java b/src/main/java/dto/PlayerDto.java deleted file mode 100644 index 8f62658b6bd..00000000000 --- a/src/main/java/dto/PlayerDto.java +++ /dev/null @@ -1,9 +0,0 @@ -package dto; - -import java.util.List; - -public record PlayerDto( - String name, - List cards -) { -} From fb55d625b3610cabb25fd39dcd7cd16371e85bfe Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Thu, 12 Mar 2026 16:45:00 +0900 Subject: [PATCH 114/129] fix : delete comment --- src/main/java/controller/BlackJackController.java | 1 - 1 file changed, 1 deletion(-) diff --git a/src/main/java/controller/BlackJackController.java b/src/main/java/controller/BlackJackController.java index f86a7d3a7ee..33a591538ee 100644 --- a/src/main/java/controller/BlackJackController.java +++ b/src/main/java/controller/BlackJackController.java @@ -10,7 +10,6 @@ import view.InputView; import view.OutputView; -// 게임의 총 흐름을 담당. public class BlackJackController { private final InputView inputView; private final OutputView outputView; From 02458cedb4b51195a135862370e779720e69c04f Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Thu, 12 Mar 2026 16:45:35 +0900 Subject: [PATCH 115/129] docs : update docs to reflect newest proj contents --- README.md | 111 ++++++++++++++++++++++++++++-------------------------- 1 file changed, 57 insertions(+), 54 deletions(-) diff --git a/README.md b/README.md index ffd790b62e5..70b16d15c36 100644 --- a/README.md +++ b/README.md @@ -6,91 +6,94 @@ ### 입력 -- [x] 플레이어의 이름을 입력받는다. +- 플레이어의 이름을 입력받는다. - **(예외 처리)** 공백 문자열인 경우 -- [x] hit(`y`) 혹은 stand(`n`)를 입력받는다. +- hit(`y`) 혹은 stand(`n`)를 입력받는다. + - **(예외 처리)** 공백 문자열인 경우 - **(예외 처리)** `y` 혹은 `n`이 아닌 경우 ### 출력 -- [x] 게임 안내 문구를 출력한다. - -- [x] 초기 카드 내역을 출력한다. - -- [x] 최종 카드 내역을 출력한다. - -- [x] 최종 승패 결과를 출력한다. +- 게임 안내 문구를 출력한다. + - 이름 입력 안내 + - HIT 혹은 STAND 여부 질문 + - 딜러의 카드 추가 여부 +- 초기 카드 내역을 출력한다. +- 최종 카드 내역을 출력한다. +- 최종 승패 결과를 출력한다. ## 도메인 -### Players +### MultiPlayers -- [x] 플레이어들을 생성한다. +- 플레이어들 생성 및 관리 + - 이름 중복 검증 +- 플레이 가능한 사용자 찾기 +- 특정 플레이어에게 hit, stand 지시 +- 플레이어들의 게임 결과 조사 +- 플레이어들의 초기 카드 정보 조사 ### Participant -- [x] 버스트 판정 - -- [x] 총합 구하기 - -- [x] 카드 받기 - -- [x] 이름 검증 +- 이름 검증 - 공백 불가 - 한국어, 알파벳만 허용 +- Hand를 이용한 보유 카드 정보 제공 + - 초기 카드 + - 구성 + - 합 +- Hand를 이용한 Bust 정보 제공 +- Hand를 이용한 BlackJack 여부 제공 ### Dealer -- [x] 카드를 받는다. - - 점수 합이 16 이하면 카드를 한장 더 받는다. +- 조건에 따른 카드 추가 + - 점수 합이 16 이하인 경우 ### Player -- [x] 카드를 받는다. - - `hit`를 선택하면 한 장 더 받는다. +- 게임 진행 가능 여부 판별 + - Hand를 이용하여 isBust 판별 +- 게임 진행 의사 관리 +- 초기 보유 카드 보여주기 +- Hand를 이용한 카드 추가 +- 조건에 따른 카드 추가 + - 버스트 상태가 아닐 경우 ### Card -- [x] 카드 점수 계산 - - 숫자 카드는 숫자 그대로, J/Q/K는 10점 +- 카드 정보 관리 및 제공 ### Deck -- [x] 카드 덱 생성 - - 전체 카드 덱 + 셔플 - - 참가자 카드 덱 생성 - -- [x] 카드 N장 뽑기 - -- [x] Ace카드를 제외한 카드들의 합계 - - 여러 카드의 점수 합산 - -- [x] 버스트 판정 - - 합계가 21 초과인지 판단 - -- [x] Ace의 1/11 처리 - - 합계에 따라 1 또는 11로 계산 - -- [x] 카드 넣기 - -### Game +- 전체 카드 관리 +- 카드 1장 뽑기 +- 카드 2장 뽑기 -- [x] 게임에 필요한 객체들을 생성한다. - - 전체 덱 객체를 생성한다. - - 딜러 객체를 생성한다. - - 플레이어 객체들을 생성한다. +### Hand -- [x] 게임을 진행한다 -- [x] 게임 +- 사용자 보유 카드 관리 +- Bust 판정 +- blackJack 판정 +- 게임 조건에 따른 카드 추가 +- 총점 계산 -## Delegate +### BlackJackGame -- `Controller` 와 `Game` 의 상호 작용을 위한 도입 +- 게임 준비 + - 딜러, 플레이어들, 전체 덱 생성 +- 게임의 turn 관리. +- 특정 사용자를 찾아 hit / stand 실행 +- 모든 게임 참가자들의 초기 정보 조사 +- 모든 게임 참가자들의 결과 조사 ### BlackJackController -- [x] 플레이어 이름을 입력받아 게임에 전달한다 - - 딜러와 플레이어에게 각각 전체 덱에서 카드를 뽑아 초기 덱을 지급한다. -- [x] 플레이어의 `hit` or `stand` 여부를 게임이 전달한다. -- [x] 게임에서 결과를 받아 `OutputView` 에 전달한다 \ No newline at end of file +- 게임 전반에서 필요한 I/O 관리 + - 게임 진행 상황 출력 관리 + - 초기 상태 + - 특정 단계에서의 사용자 정보 + - 사용자 이름 입력 관리 + - hit 혹은 stand 여부 입력 관리 + - 게임 결과 출력 관리 \ No newline at end of file From ef06868ea3342b4ee6635d238735482b8782cdd7 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Thu, 12 Mar 2026 18:37:36 +0900 Subject: [PATCH 116/129] fix : wrapping name to meet programming req --- src/main/java/domain/Participant.java | 27 ++-------- src/main/java/domain/ParticipantName.java | 26 ++++++++++ src/main/java/domain/Player.java | 4 +- src/test/java/domain/ParticipantNameTest.java | 49 +++++++++++++++++++ 4 files changed, 80 insertions(+), 26 deletions(-) create mode 100644 src/main/java/domain/ParticipantName.java create mode 100644 src/test/java/domain/ParticipantNameTest.java diff --git a/src/main/java/domain/Participant.java b/src/main/java/domain/Participant.java index 68363572eaf..46ae087859c 100644 --- a/src/main/java/domain/Participant.java +++ b/src/main/java/domain/Participant.java @@ -1,41 +1,20 @@ package domain; -import common.ErrorMessage; import java.util.List; -import java.util.regex.Pattern; public abstract class Participant { protected final Hand hand; - protected final String name; + protected final ParticipantName participantName; protected Participant(String name, Hand hand) { this.hand = hand; - validateName(name); - this.name = name; - } - - private static void validateName(String name) { - validateIsNotBlank(name); - validateKoreanAndEnglish(name); - } - - private static void validateKoreanAndEnglish(String name) { - Pattern NAME_PATTERN = Pattern.compile("^[a-zA-Z가-힣]+$"); - if (!NAME_PATTERN.matcher(name).matches()) { - throw new IllegalArgumentException(ErrorMessage.ONLY_KO_AND_ENG.getMessage()); - } - } - - private static void validateIsNotBlank(String name) { - if (name == null || name.isBlank()) { - throw new IllegalArgumentException(ErrorMessage.NOT_ALLOW_EMPTY_INPUT.getMessage()); - } + this.participantName = new ParticipantName(name); } public abstract List showInitialCard(); public String getName() { - return name; + return participantName.name(); } public List showOwnCards() { diff --git a/src/main/java/domain/ParticipantName.java b/src/main/java/domain/ParticipantName.java new file mode 100644 index 00000000000..20dcf864a4c --- /dev/null +++ b/src/main/java/domain/ParticipantName.java @@ -0,0 +1,26 @@ +package domain; + +import common.ErrorMessage; +import java.util.regex.Pattern; + +public record ParticipantName(String name) { + + private static final Pattern NAME_PATTERN = Pattern.compile("^[a-zA-Z가-힣]+$"); + + public ParticipantName { + validateIsNotBlank(name); + validateKoreanAndEnglish(name); + } + + private void validateKoreanAndEnglish(String value) { + if (!NAME_PATTERN.matcher(value).matches()) { + throw new IllegalArgumentException(ErrorMessage.ONLY_KO_AND_ENG.getMessage()); + } + } + + private void validateIsNotBlank(String value) { + if (value == null || value.isBlank()) { + throw new IllegalArgumentException(ErrorMessage.NOT_ALLOW_EMPTY_INPUT.getMessage()); + } + } +} diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java index 8b1ef3f5de3..088183e5433 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/Player.java @@ -22,11 +22,11 @@ public Player hit(Supplier cardSupplier) { } Hand newHand = hand.addCard(cardSupplier.get()); - return new Player(this.name, newHand, false); + return new Player(this.participantName.name(), newHand, false); } public Player stand() { - return new Player(this.name, this.hand, true); + return new Player(this.participantName.name(), this.hand, true); } public boolean isFinished() { diff --git a/src/test/java/domain/ParticipantNameTest.java b/src/test/java/domain/ParticipantNameTest.java new file mode 100644 index 00000000000..d64203251a6 --- /dev/null +++ b/src/test/java/domain/ParticipantNameTest.java @@ -0,0 +1,49 @@ +package domain; + + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import common.ErrorMessage; +import org.junit.jupiter.api.Assertions; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class ParticipantNameTest { + @Test + @DisplayName("공백은 이름으로 쓸 수 없다") + void not_allow_empty() { + //given + String blank = " "; + + //when, then + assertThatThrownBy( + () -> new ParticipantName(blank) + ).isInstanceOf(IllegalArgumentException.class) + .hasMessage(ErrorMessage.NOT_ALLOW_EMPTY_INPUT.getMessage()); + } + + @Test + @DisplayName("특수문자, 숫자는 이름에 포함될 수 없다") + void not_allow_special_character() { + //given + String nameWithSpecialChar = "asdf_123"; + + //when, then + assertThatThrownBy( + () -> new ParticipantName(nameWithSpecialChar) + ).isInstanceOf(IllegalArgumentException.class) + .hasMessage(ErrorMessage.ONLY_KO_AND_ENG.getMessage()); + } + + @Test + @DisplayName("한국어,영어로 이루어진 이름은 좋다") + void name_success() { + //given + String name = "rati"; + + //when, then + Assertions.assertDoesNotThrow( + () -> new ParticipantName(name) + ); + } +} \ No newline at end of file From ddb8b64c4dbcfa03434fc6c1743aaf7dd67814d2 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Thu, 12 Mar 2026 21:01:06 +0900 Subject: [PATCH 117/129] fix : adopt state pattern to meet programming requirement MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 모든 원시 값과 문자열을 포장한다라는 프로그래밍 요구 사항과 3개 이상의 인스턴스 변수를 가진 클래스를 쓰지 않는다라는 요구사항을 충족하기 위해 상태 패턴 도입 --- src/main/java/common/ErrorMessage.java | 1 + src/main/java/domain/BlackJackGame.java | 17 ++++-- src/main/java/domain/Dealer.java | 20 +++---- src/main/java/domain/GameResult.java | 18 +++--- src/main/java/domain/MultiPlayers.java | 8 ++- src/main/java/domain/Participant.java | 27 ++++----- src/main/java/domain/Player.java | 35 ++++++------ .../java/domain/state/BlackJackGameState.java | 9 +++ src/main/java/domain/state/BustGameState.java | 9 +++ .../java/domain/state/CanHitGameState.java | 23 ++++++++ .../java/domain/state/CommonGameState.java | 49 ++++++++++++++++ .../domain/state/DealerCanHitGameState.java | 35 ++++++++++++ src/main/java/domain/state/EndGameState.java | 27 +++++++++ src/main/java/domain/state/GameState.java | 39 +++++++++++++ .../domain/state/PlayerCanHitGameState.java | 25 ++++++++ src/main/java/domain/state/StayGameState.java | 9 +++ src/main/java/dto/PlayerResultDto.java | 6 +- src/test/java/domain/DealerTest.java | 39 +++++++------ src/test/java/domain/GameResultTest.java | 57 +++++++------------ src/test/java/domain/MultiPlayersTest.java | 17 ++++-- src/test/java/domain/PlayerTest.java | 54 ++++++++++++------ 21 files changed, 387 insertions(+), 137 deletions(-) create mode 100644 src/main/java/domain/state/BlackJackGameState.java create mode 100644 src/main/java/domain/state/BustGameState.java create mode 100644 src/main/java/domain/state/CanHitGameState.java create mode 100644 src/main/java/domain/state/CommonGameState.java create mode 100644 src/main/java/domain/state/DealerCanHitGameState.java create mode 100644 src/main/java/domain/state/EndGameState.java create mode 100644 src/main/java/domain/state/GameState.java create mode 100644 src/main/java/domain/state/PlayerCanHitGameState.java create mode 100644 src/main/java/domain/state/StayGameState.java diff --git a/src/main/java/common/ErrorMessage.java b/src/main/java/common/ErrorMessage.java index ce52691ac52..5271d81586d 100644 --- a/src/main/java/common/ErrorMessage.java +++ b/src/main/java/common/ErrorMessage.java @@ -9,6 +9,7 @@ public enum ErrorMessage { NAME_UNIQUENESS_ERR("이름은 중복되면 안됩니다"), PLAYER_NOT_FOUND("해당 플레이어를 찾을 수 없습니다"), NO_MORE_PLAYABLE_PLAYER("더 이상 게임을 진행할 수 있는 플레이어가 없습니다."), + NOT_ALLOW_METHOD_CALL("현재 상태에서 허용되지 않는 메소드 호출입니다"), MAX_PLAYER_ERROR("최대 인원을 초과했습니다."); private final String message; diff --git a/src/main/java/domain/BlackJackGame.java b/src/main/java/domain/BlackJackGame.java index 97b60d942d5..cf0749a12de 100644 --- a/src/main/java/domain/BlackJackGame.java +++ b/src/main/java/domain/BlackJackGame.java @@ -1,6 +1,7 @@ package domain; import common.ErrorMessage; +import domain.state.GameState; import dto.DealerResultDto; import dto.GameResultDto; import dto.ParticipantDto; @@ -30,9 +31,13 @@ public static BlackJackGame ready(List playerNames, CardCreationStrategy private static Dealer createNewDealer(Deck totalDeck) { List dealersInitialCards = totalDeck.drawTwoCards(); - Card card1 = dealersInitialCards.get(0); - Card card2 = dealersInitialCards.get(1); - return Dealer.from(Hand.of(card1, card2)); + Hand initialDealerHand = Hand.of( + dealersInitialCards.get(0), + dealersInitialCards.get(1) + ); + return Dealer.from( + GameState.createDealerInitialGameState(initialDealerHand) + ); } public Optional whoseTurn() { @@ -54,9 +59,9 @@ public ParticipantDto doStandProcess() { } public boolean doDealerHitOrStandProcess() { - Optional nextDealer = dealer.addCard(totalDeck::drawCard); - nextDealer.ifPresent(this::updateDealer); - return nextDealer.isPresent(); + Dealer newDealer = dealer.addCard(totalDeck::drawCard); + updateDealer(newDealer); + return !newDealer.gameState.isFinished(); } public List getPlayersGameSettingStates() { diff --git a/src/main/java/domain/Dealer.java b/src/main/java/domain/Dealer.java index 3c7e1cdeac3..311bd4cdd08 100644 --- a/src/main/java/domain/Dealer.java +++ b/src/main/java/domain/Dealer.java @@ -1,19 +1,18 @@ package domain; +import domain.state.GameState; import java.util.List; -import java.util.Optional; import java.util.function.Supplier; public class Dealer extends Participant { - private static final int MINIMUM_TOTAL_SCORE = 16; private static final String DEALER_NAME = "딜러"; - private Dealer(Hand hand) { - super(DEALER_NAME, hand); + private Dealer(GameState gameState) { + super(DEALER_NAME, gameState); } - public static Dealer from(Hand hand) { - return new Dealer(hand); + public static Dealer from(GameState gameState) { + return new Dealer(gameState); } @Override @@ -22,11 +21,8 @@ public List showInitialCard() { return List.of(ownCards.getFirst()); } - public Optional addCard(Supplier cardSupplier) { - if (hand.calculateCardScoreSum() <= MINIMUM_TOTAL_SCORE) { - Hand newHand = this.hand.addCard(cardSupplier.get()); - return Optional.of(new Dealer(newHand)); - } - return Optional.empty(); + public Dealer addCard(Supplier cardSupplier) { + GameState newGameState = gameState.hit(cardSupplier); + return Dealer.from(newGameState); } } diff --git a/src/main/java/domain/GameResult.java b/src/main/java/domain/GameResult.java index 8f9faebfda4..e349546e6a5 100644 --- a/src/main/java/domain/GameResult.java +++ b/src/main/java/domain/GameResult.java @@ -1,28 +1,30 @@ package domain; +import domain.state.GameState; + public enum GameResult { 승, 무, 패; - public static GameResult decidePlayerResult(Dealer dealer, Player player) { - if (player.isBust()) { + public static GameResult decidePlayerResult(GameState playerGameState, GameState dealerGameState) { + if (playerGameState.isBust()) { return GameResult.패; } - if (dealer.isBust()) { + if (dealerGameState.isBust()) { return GameResult.승; } - if (player.isBlackJack() && dealer.isBlackJack()) { + if (playerGameState.isBlackJack() && dealerGameState.isBlackJack()) { return GameResult.무; } - if (player.isBlackJack()) { + if (playerGameState.isBlackJack()) { return GameResult.승; } - if (dealer.isBlackJack()) { + if (dealerGameState.isBlackJack()) { return GameResult.패; } - return compareScoreForCheckPlayerResult(dealer.getOwnCardsSum(), player.getOwnCardsSum()); + return compareScoreForCheckPlayerResult(playerGameState.getCardsSum(), dealerGameState.getCardsSum()); } - private static GameResult compareScoreForCheckPlayerResult(int dealerScore, int playerScore) { + private static GameResult compareScoreForCheckPlayerResult(int playerScore, int dealerScore) { if (dealerScore > playerScore) { return GameResult.패; } diff --git a/src/main/java/domain/MultiPlayers.java b/src/main/java/domain/MultiPlayers.java index e10719ed26b..fb78bcf9ab3 100644 --- a/src/main/java/domain/MultiPlayers.java +++ b/src/main/java/domain/MultiPlayers.java @@ -1,6 +1,7 @@ package domain; import common.ErrorMessage; +import domain.state.GameState; import dto.ParticipantDto; import dto.PlayerResultDto; import java.util.ArrayList; @@ -35,7 +36,10 @@ private static List createPlayers(List playerNames, Deck totalDe private static Player createNewPlayer(Deck totalDeck, String name) { List twoCards = totalDeck.drawTwoCards(); - return Player.from(name, Hand.of(twoCards.get(0), twoCards.get(1))); + Hand newPlayerHand = Hand.of(twoCards.get(0), twoCards.get(1)); + return Player.from( + name, + GameState.createPlayerInitialGameState(newPlayerHand)); } private static void validateNameUniqueness(List playerNames) { @@ -73,7 +77,7 @@ public List getInitialStates() { public List checkPlayersGameResult(Dealer dealer) { return players.stream().map( - player -> PlayerResultDto.from(player, dealer) + player -> PlayerResultDto.from(player, dealer.gameState) ).toList(); } diff --git a/src/main/java/domain/Participant.java b/src/main/java/domain/Participant.java index 46ae087859c..df54ad2b6e8 100644 --- a/src/main/java/domain/Participant.java +++ b/src/main/java/domain/Participant.java @@ -1,14 +1,15 @@ package domain; +import domain.state.GameState; import java.util.List; public abstract class Participant { - protected final Hand hand; + protected final GameState gameState; protected final ParticipantName participantName; - protected Participant(String name, Hand hand) { - this.hand = hand; + protected Participant(String name, GameState gameState) { this.participantName = new ParticipantName(name); + this.gameState = gameState; } public abstract List showInitialCard(); @@ -18,18 +19,18 @@ public String getName() { } public List showOwnCards() { - return hand.showCards(); + return gameState.showOwnCards(); } public int getOwnCardsSum() { - return this.hand.calculateCardScoreSum(); - } - - public boolean isBust() { - return hand.isBust(); - } - - public boolean isBlackJack() { - return hand.isBlackJack(); + return this.gameState.getCardsSum(); } +// +// public boolean isBust() { +// return gameState.is(); +// } +// +// public boolean isBlackJack() { +// return hand.isBlackJack(); +// } } \ No newline at end of file diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java index 088183e5433..a42adc58f9c 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/Player.java @@ -1,40 +1,41 @@ package domain; +import domain.state.GameState; import java.util.List; import java.util.Objects; import java.util.function.Supplier; public class Player extends Participant { - private final boolean isStay; - - private Player(String name, Hand hand, boolean isStay) { - super(name, hand); - this.isStay = isStay; + private Player(String name, GameState gameState) { + super(name, gameState); } - public static Player from(String name, Hand hand) { - return new Player(name, hand, false); + public static Player from(String name, GameState gameState) { + return new Player(name, gameState); } public Player hit(Supplier cardSupplier) { - if (isFinished()) { - return this; - } - - Hand newHand = hand.addCard(cardSupplier.get()); - return new Player(this.participantName.name(), newHand, false); + GameState newGameState = gameState.hit(cardSupplier); + return new Player( + this.participantName.name(), + newGameState + ); } public Player stand() { - return new Player(this.participantName.name(), this.hand, true); + GameState newStandGameState = gameState.stay(); + return new Player( + this.participantName.name(), + newStandGameState + ); } public boolean isFinished() { - return hand.isBust() || hand.isFull() || isStay; + return gameState.isFinished(); } - public GameResult compare(Dealer dealer) { - return GameResult.decidePlayerResult(dealer, this); + public GameResult calculateGameResult(GameState dealerGameState) { + return gameState.compare(gameState, dealerGameState); } @Override diff --git a/src/main/java/domain/state/BlackJackGameState.java b/src/main/java/domain/state/BlackJackGameState.java new file mode 100644 index 00000000000..ad1daa32d6b --- /dev/null +++ b/src/main/java/domain/state/BlackJackGameState.java @@ -0,0 +1,9 @@ +package domain.state; + +import domain.Hand; + +public class BlackJackGameState extends EndGameState { + public BlackJackGameState(Hand hand) { + super(hand); + } +} \ No newline at end of file diff --git a/src/main/java/domain/state/BustGameState.java b/src/main/java/domain/state/BustGameState.java new file mode 100644 index 00000000000..3c3ee63ddd6 --- /dev/null +++ b/src/main/java/domain/state/BustGameState.java @@ -0,0 +1,9 @@ +package domain.state; + +import domain.Hand; + +public class BustGameState extends EndGameState { + public BustGameState(Hand hand) { + super(hand); + } +} diff --git a/src/main/java/domain/state/CanHitGameState.java b/src/main/java/domain/state/CanHitGameState.java new file mode 100644 index 00000000000..7ee31d331e1 --- /dev/null +++ b/src/main/java/domain/state/CanHitGameState.java @@ -0,0 +1,23 @@ +package domain.state; + +import domain.Card; +import domain.Hand; +import java.util.function.Supplier; + +public abstract class CanHitGameState extends CommonGameState { + + public CanHitGameState(Hand hand) { + super(hand); + } + + @Override + public abstract GameState hit(Supplier cardSupplier); + + @Override + public abstract GameState stay(); + + @Override + public boolean isFinished() { + return false; + } +} \ No newline at end of file diff --git a/src/main/java/domain/state/CommonGameState.java b/src/main/java/domain/state/CommonGameState.java new file mode 100644 index 00000000000..3e5c4643b28 --- /dev/null +++ b/src/main/java/domain/state/CommonGameState.java @@ -0,0 +1,49 @@ +package domain.state; + +import domain.Card; +import domain.GameResult; +import domain.Hand; +import java.util.List; +import java.util.function.Supplier; + +public abstract class CommonGameState implements GameState { + protected final Hand hand; + + public CommonGameState(Hand hand) { + this.hand = hand; + } + + @Override + public abstract GameState hit(Supplier cardSupplier); + + @Override + public abstract GameState stay(); + + @Override + public boolean isBlackJack() { + return hand.isBlackJack(); + } + + @Override + public boolean isBust() { + return hand.isBust(); + } + + @Override + public abstract boolean isFinished(); + + @Override + public List showOwnCards() { + return hand.showCards(); + } + + @Override + public int getCardsSum() { + return hand.calculateCardScoreSum(); + } + + @Override + public GameResult compare(GameState playerState, GameState dealerState) { + return GameResult.decidePlayerResult(playerState, dealerState); + } +} diff --git a/src/main/java/domain/state/DealerCanHitGameState.java b/src/main/java/domain/state/DealerCanHitGameState.java new file mode 100644 index 00000000000..396ef39188b --- /dev/null +++ b/src/main/java/domain/state/DealerCanHitGameState.java @@ -0,0 +1,35 @@ +package domain.state; + +import domain.Card; +import domain.Hand; +import java.util.function.Supplier; + +public class DealerCanHitGameState extends CanHitGameState { + private static final int MINIMUM_TOTAL_SCORE = 16; + + public DealerCanHitGameState(Hand hand) { + super(hand); + } + + @Override + public GameState hit(Supplier cardSupplier) { + Hand newHand = this.hand.addCard(cardSupplier.get()); + if (newHand.isBust()) { + return new BustGameState(newHand); + } + + if (newHand.calculateCardScoreSum() > MINIMUM_TOTAL_SCORE) { + return new StayGameState(newHand); + } + + return new DealerCanHitGameState(newHand); + } + + @Override + public GameState stay() { + if (hand.calculateCardScoreSum() > MINIMUM_TOTAL_SCORE) { + return new StayGameState(hand); + } + return this; + } +} diff --git a/src/main/java/domain/state/EndGameState.java b/src/main/java/domain/state/EndGameState.java new file mode 100644 index 00000000000..8034c42ad9b --- /dev/null +++ b/src/main/java/domain/state/EndGameState.java @@ -0,0 +1,27 @@ +package domain.state; + +import common.ErrorMessage; +import domain.Card; +import domain.Hand; +import java.util.function.Supplier; + +public abstract class EndGameState extends CommonGameState { + public EndGameState(Hand hand) { + super(hand); + } + + @Override + public GameState hit(Supplier cardSupplier) { + throw new IllegalStateException(ErrorMessage.NOT_ALLOW_METHOD_CALL.getMessage()); + } + + @Override + public GameState stay() { + throw new IllegalStateException(ErrorMessage.NOT_ALLOW_METHOD_CALL.getMessage()); + } + + @Override + public boolean isFinished() { + return true; + } +} diff --git a/src/main/java/domain/state/GameState.java b/src/main/java/domain/state/GameState.java new file mode 100644 index 00000000000..56c3c3d0d07 --- /dev/null +++ b/src/main/java/domain/state/GameState.java @@ -0,0 +1,39 @@ +package domain.state; + +import domain.Card; +import domain.GameResult; +import domain.Hand; +import java.util.List; +import java.util.function.Supplier; + +public interface GameState { + static GameState createPlayerInitialGameState(Hand hand) { + if (hand.isBlackJack()) { + return new BlackJackGameState(hand); + } + return new PlayerCanHitGameState(hand); + } + + static GameState createDealerInitialGameState(Hand hand) { + if (hand.isBlackJack()) { + return new BlackJackGameState(hand); + } + return new DealerCanHitGameState(hand); + } + + GameState hit(Supplier cardSupplier); + + GameState stay(); + + boolean isBlackJack(); + + boolean isBust(); + + boolean isFinished(); + + List showOwnCards(); + + int getCardsSum(); + + GameResult compare(GameState playerState, GameState dealerState); +} \ No newline at end of file diff --git a/src/main/java/domain/state/PlayerCanHitGameState.java b/src/main/java/domain/state/PlayerCanHitGameState.java new file mode 100644 index 00000000000..9d731953e68 --- /dev/null +++ b/src/main/java/domain/state/PlayerCanHitGameState.java @@ -0,0 +1,25 @@ +package domain.state; + +import domain.Card; +import domain.Hand; +import java.util.function.Supplier; + +public class PlayerCanHitGameState extends CanHitGameState { + public PlayerCanHitGameState(Hand hand) { + super(hand); + } + + @Override + public GameState hit(Supplier cardSupplier) { + Hand newHand = hand.addCard(cardSupplier.get()); + if (newHand.isBust()) { + return new BustGameState(newHand); + } + return new PlayerCanHitGameState(newHand); + } + + @Override + public GameState stay() { + return new StayGameState(hand); + } +} diff --git a/src/main/java/domain/state/StayGameState.java b/src/main/java/domain/state/StayGameState.java new file mode 100644 index 00000000000..4b1daee6551 --- /dev/null +++ b/src/main/java/domain/state/StayGameState.java @@ -0,0 +1,9 @@ +package domain.state; + +import domain.Hand; + +public class StayGameState extends EndGameState { + public StayGameState(Hand hand) { + super(hand); + } +} diff --git a/src/main/java/dto/PlayerResultDto.java b/src/main/java/dto/PlayerResultDto.java index 063362e620b..54e790762ed 100644 --- a/src/main/java/dto/PlayerResultDto.java +++ b/src/main/java/dto/PlayerResultDto.java @@ -1,19 +1,19 @@ package dto; -import domain.Dealer; import domain.GameResult; import domain.Player; +import domain.state.GameState; public record PlayerResultDto( ParticipantDto playerDto, int score, GameResult result ) { - public static PlayerResultDto from(Player player, Dealer dealer) { + public static PlayerResultDto from(Player player, GameState dealerGameState) { return new PlayerResultDto( ParticipantDto.from(player), player.getOwnCardsSum(), - player.compare(dealer) + player.calculateGameResult(dealerGameState) ); } } diff --git a/src/test/java/domain/DealerTest.java b/src/test/java/domain/DealerTest.java index b6c5027c673..a07d396958c 100644 --- a/src/test/java/domain/DealerTest.java +++ b/src/test/java/domain/DealerTest.java @@ -1,29 +1,31 @@ package domain; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; -import static org.junit.jupiter.api.Assertions.assertTrue; +import domain.state.GameState; import java.util.ArrayDeque; import java.util.List; -import java.util.Optional; +import org.junit.jupiter.api.Assertions; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; public class DealerTest { - private static Hand dealerHand = Hand.of( - new Card(CardShape.스페이드, CardContents.J), - new Card(CardShape.클로버, CardContents.FIVE) - ); - @Test @DisplayName("Dealer를 생성할 때 오류 발생 안함") void dealer_create_success() { - assertDoesNotThrow(() -> Dealer.from(dealerHand)); + GameState dealerGameState = GameState.createDealerInitialGameState( + Hand.of( + new Card(CardShape.스페이드, CardContents.J), + new Card(CardShape.클로버, CardContents.FIVE) + + ) + ); + assertDoesNotThrow(() -> Dealer.from(dealerGameState)); } @Test - @DisplayName("딜러는 카드의 합이 16 이하면 카드를 한 장 더 받고 새로운 Dealer를 반환한다") + @DisplayName("딜러는 카드의 합이 16 이하면 카드를 한 장 더 받을 수 있다") void addCard_success() { //given CardCreationStrategy onlyJCardCreation = () -> { @@ -32,16 +34,17 @@ void addCard_success() { }; Deck totalDeck = Deck.createDeck(onlyJCardCreation); - Hand dealerHandSixteen = Hand.of( - new Card(CardShape.하트, CardContents.SIX), - new Card(CardShape.스페이드, CardContents.TEN) + GameState dealerGameState = GameState.createDealerInitialGameState( + Hand.of( + new Card(CardShape.하트, CardContents.SIX), + new Card(CardShape.스페이드, CardContents.TEN) + ) ); - Dealer dealer = Dealer.from(dealerHandSixteen); + Dealer dealer = Dealer.from(dealerGameState); - //when - Optional result = dealer.addCard(totalDeck::drawCard); - - //then - assertTrue(result.isPresent()); + //when, then + Assertions.assertDoesNotThrow( + () -> dealer.addCard(totalDeck::drawCard) + ); } } diff --git a/src/test/java/domain/GameResultTest.java b/src/test/java/domain/GameResultTest.java index effc02190b1..4a740c0d132 100644 --- a/src/test/java/domain/GameResultTest.java +++ b/src/test/java/domain/GameResultTest.java @@ -2,10 +2,10 @@ import static org.junit.jupiter.api.Assertions.assertEquals; +import domain.state.GameState; import java.util.ArrayDeque; import java.util.LinkedList; import java.util.List; -import java.util.Optional; import java.util.Queue; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -34,16 +34,11 @@ class blackJackCaseTest { @DisplayName("플레이어가 블랙잭이면 플레이어가 이긴 결과를 도출한다") void decidePlayerResult_player_blackjack() { //given - Dealer testDealer = Dealer.from( - normalHand - ); - Player testPlayer = Player.from( - "gump", - blackJackHand - ); + GameState dealerInitialGameState = GameState.createDealerInitialGameState(normalHand); + GameState playerInitialGameState = GameState.createPlayerInitialGameState(blackJackHand); //when - GameResult result = GameResult.decidePlayerResult(testDealer, testPlayer); + GameResult result = GameResult.decidePlayerResult(playerInitialGameState, dealerInitialGameState); //then assertEquals(GameResult.승, result); @@ -53,16 +48,11 @@ void decidePlayerResult_player_blackjack() { @DisplayName("딜러가 블랙잭이면 플레이어가 진 결과를 도출한다") void decidePlayerResult_dealer_blackjack() { //given - Dealer testDealer = Dealer.from( - blackJackHand - ); - Player testPlayer = Player.from( - "gump", - normalHand - ); + GameState dealerGameState = GameState.createDealerInitialGameState(blackJackHand); + GameState playerGameState = GameState.createPlayerInitialGameState(normalHand); //when - GameResult result = GameResult.decidePlayerResult(testDealer, testPlayer); + GameResult result = GameResult.decidePlayerResult(playerGameState, dealerGameState); //then assertEquals(GameResult.패, result); @@ -76,16 +66,12 @@ void decidePlayerResult_both_blackjack() { new Card(CardShape.하트, CardContents.A), new Card(CardShape.하트, CardContents.TEN) ); - Dealer testDealer = Dealer.from( - blackJackHand - ); - Player testPlayer = Player.from( - "gump", - anotherBlackJackHand - ); + + GameState dealerGameState = GameState.createDealerInitialGameState(blackJackHand); + GameState playerGameState = GameState.createPlayerInitialGameState(anotherBlackJackHand); //when - GameResult result = GameResult.decidePlayerResult(testDealer, testPlayer); + GameResult result = GameResult.decidePlayerResult(playerGameState, dealerGameState); //then assertEquals(GameResult.무, result); @@ -107,17 +93,16 @@ class bustCaseTest { @DisplayName("플레이어가 bust 이면 패 결과를 반환") void player_bust_case() { //given - Dealer testDealer = Dealer.from( - normalHand - ); + GameState dealerGameState = GameState.createDealerInitialGameState(normalHand); + GameState playerGameState = GameState.createPlayerInitialGameState(handThatValue16); Player testPlayer = Player.from( "gump", - handThatValue16 + playerGameState ); testPlayer = testPlayer.hit(testTotalDeck::drawCard); //when - GameResult result = GameResult.decidePlayerResult(testDealer, testPlayer); + GameResult result = GameResult.decidePlayerResult(testPlayer.gameState, dealerGameState); //then assertEquals(GameResult.패, result); @@ -127,17 +112,15 @@ void player_bust_case() { @DisplayName("딜러가 bust 이면 승 결과를 반환") void dealer_bust_case() { //given + GameState dealerGameState = GameState.createDealerInitialGameState(handThatValue16); + GameState playerGameState = GameState.createPlayerInitialGameState(normalHand); Dealer testDealer = Dealer.from( - handThatValue16 - ); - Player testPlayer = Player.from( - "gump", - normalHand + dealerGameState ); - Optional dealer = testDealer.addCard(testTotalDeck::drawCard); + Dealer newDealer = testDealer.addCard(testTotalDeck::drawCard); //when - GameResult result = GameResult.decidePlayerResult(dealer.get(), testPlayer); + GameResult result = GameResult.decidePlayerResult(playerGameState, newDealer.gameState); //then assertEquals(GameResult.승, result); diff --git a/src/test/java/domain/MultiPlayersTest.java b/src/test/java/domain/MultiPlayersTest.java index 5c9fd116f7d..70e3c28978a 100644 --- a/src/test/java/domain/MultiPlayersTest.java +++ b/src/test/java/domain/MultiPlayersTest.java @@ -7,6 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import common.ErrorMessage; +import domain.state.GameState; import dto.ParticipantDto; import dto.PlayerResultDto; import java.util.ArrayDeque; @@ -148,7 +149,10 @@ void getInitialState_success() { CardCreationStrategy onlyTwoTenCardsCreationStrategy = () -> new ArrayDeque<>(onlyTwoTenCards); MultiPlayers multiPlayers = MultiPlayers.of(onlyOneNames, Deck.createDeck(onlyTwoTenCardsCreationStrategy)); ParticipantDto expect = ParticipantDto.from( - Player.from(testerName, Hand.of(heartTen, heartJ)) + Player.from( + testerName, + GameState.createPlayerInitialGameState(Hand.of(heartTen, heartJ)) + ) ); //when @@ -171,11 +175,16 @@ void checkPlayersGameResult_success() { Card heartJ = new Card(CardShape.하트, CardContents.J); Queue onlyTwoTenCards = new LinkedList<>(List.of(heartTen, heartJ)); - Dealer testDealer = Dealer.from(Hand.of(heartTwo, heartThree)); - Player expectedPlayer = Player.from(testerName, Hand.of(heartTen, heartJ)); + Dealer testDealer = Dealer.from( + GameState.createDealerInitialGameState(Hand.of(heartTwo, heartThree)) + ); + Player expectedPlayer = Player.from( + testerName, + GameState.createPlayerInitialGameState(Hand.of(heartTen, heartJ)) + ); CardCreationStrategy onlyTwoTenCardsCreationStrategy = () -> new ArrayDeque<>(onlyTwoTenCards); MultiPlayers multiPlayers = MultiPlayers.of(onlyOneNames, Deck.createDeck(onlyTwoTenCardsCreationStrategy)); - PlayerResultDto expect = PlayerResultDto.from(expectedPlayer, testDealer); + PlayerResultDto expect = PlayerResultDto.from(expectedPlayer, testDealer.gameState); //when List result = multiPlayers.checkPlayersGameResult(testDealer); diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java index e5026f42919..049f658fc47 100644 --- a/src/test/java/domain/PlayerTest.java +++ b/src/test/java/domain/PlayerTest.java @@ -1,8 +1,11 @@ package domain; +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import common.ErrorMessage; +import domain.state.GameState; import java.util.LinkedList; import java.util.List; import java.util.Queue; @@ -24,7 +27,7 @@ void player_create_success() { String name = "pobi"; assertDoesNotThrow( - () -> Player.from(name, playerHand) + () -> Player.from(name, GameState.createPlayerInitialGameState(playerHand)) ); } @@ -43,7 +46,10 @@ class hitTest { Supplier onlyTwoTenCardSupplier = onlyTwoTenCards::poll; String testName = "gump"; - Player testPlayerWhoHoldTotal15Cards = Player.from(testName, playerHand); + Player testPlayerWhoHoldTotal15Cards = Player.from( + testName, + GameState.createPlayerInitialGameState(playerHand) + ); @Test @DisplayName("hit 할 수 있는 상태이면 hit를 진행한다") @@ -62,16 +68,16 @@ void hit_do() { } @Test - @DisplayName("hit 할 수 없는 상태이면 카드 뽑기를 내부적으로 진행하지 않는다") + @DisplayName("hit 할 수 없는 상태이면 카드 뽑기를 내부적으로 카드를 뽑지 못하도록 오류를 던진다") void hit_do_not() { //given testPlayerWhoHoldTotal15Cards = testPlayerWhoHoldTotal15Cards.hit(onlyTwoTenCardSupplier); //when - testPlayerWhoHoldTotal15Cards = testPlayerWhoHoldTotal15Cards.hit(onlyTwoTenCardSupplier); - - //then - Assertions.assertThat(onlyTwoTenCardSupplier.get()).isNotNull(); + assertThatThrownBy( + () -> testPlayerWhoHoldTotal15Cards.hit(onlyTwoTenCardSupplier) + ).isInstanceOf(IllegalStateException.class) + .hasMessage(ErrorMessage.NOT_ALLOW_METHOD_CALL.getMessage()); } @Test @@ -83,7 +89,10 @@ void hit_butBust_and_finish() { new Card(CardShape.클로버, CardContents.TEN) ); String testName = "gump"; - Player testPlayer = Player.from(testName, playerHand); + Player testPlayer = Player.from( + testName, + GameState.createPlayerInitialGameState(playerHand) + ); testPlayer.hit(onlyTwoTenCardSupplier); //when @@ -103,7 +112,10 @@ void stand_and_finish() { new Card(CardShape.클로버, CardContents.FIVE) ); String testName = "gump"; - Player testPlayer = Player.from(testName, playerHand); + Player testPlayer = Player.from( + testName, + GameState.createPlayerInitialGameState(playerHand) + ); //when testPlayer = testPlayer.stand(); @@ -125,11 +137,14 @@ void lose_when_dealer_blackjack() { new Card(CardShape.스페이드, CardContents.TWO), new Card(CardShape.클로버, CardContents.THREE) ); - Dealer testDealer = Dealer.from(blackJackHand); - Player testPlayer = Player.from(testPlayerName, notBlackJackAndNotBustHand); + GameState dealerGameState = GameState.createDealerInitialGameState(blackJackHand); + Player testPlayer = Player.from( + testPlayerName, + GameState.createPlayerInitialGameState(notBlackJackAndNotBustHand) + ); //when - GameResult result = testPlayer.compare(testDealer); + GameResult result = testPlayer.calculateGameResult(dealerGameState); //then assertEquals(GameResult.class, result.getClass()); @@ -138,6 +153,7 @@ void lose_when_dealer_blackjack() { @Test @DisplayName("이름이 같으면 같은 Player로 본다") void equal_when_name_equal() { + String testName = "gump"; Hand playerHand1 = Hand.of( new Card(CardShape.스페이드, CardContents.A), new Card(CardShape.클로버, CardContents.TWO) @@ -146,11 +162,15 @@ void equal_when_name_equal() { new Card(CardShape.하트, CardContents.THREE), new Card(CardShape.클로버, CardContents.FOUR) ); - String testName = "gump"; - Player testPlayer1 = Player.from(testName, playerHand1); - Player testPlayer2 = Player.from(testName, playerHand2); - + Player firstGump = Player.from( + testName, + GameState.createPlayerInitialGameState(playerHand1) + ); + Player secondGump = Player.from( + testName, + GameState.createPlayerInitialGameState(playerHand2) + ); //when, then - Assertions.assertThat(testPlayer1.equals(testPlayer2)).isTrue(); + Assertions.assertThat(firstGump.equals(secondGump)).isTrue(); } } \ No newline at end of file From c3d2d74e4ab177639b13a0e9cdf6b4a1e1e0d297 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Fri, 13 Mar 2026 09:48:36 +0900 Subject: [PATCH 118/129] fix : delete dependency of view at controller --- src/main/java/controller/BlackJackController.java | 8 ++++---- src/main/java/view/InputView.java | 2 +- src/main/java/view/InputViewImpl.java | 4 ++-- src/test/java/controller/BlackJackControllerTest.java | 5 +++-- 4 files changed, 10 insertions(+), 9 deletions(-) diff --git a/src/main/java/controller/BlackJackController.java b/src/main/java/controller/BlackJackController.java index 33a591538ee..e462b76d2e4 100644 --- a/src/main/java/controller/BlackJackController.java +++ b/src/main/java/controller/BlackJackController.java @@ -59,13 +59,13 @@ private void playPlayersTurn(BlackJackGame game) { } outputView.printHitOrStandPrompt(currentPlayer.getName()); - String hitOrStandInfo = retry(inputView::readHitOrStand); - doHitOrStand(hitOrStandInfo, game); + boolean wantToHit = retry(inputView::wantToHit); + doHitOrStand(wantToHit, game); } } - private void doHitOrStand(String hitOrStand, BlackJackGame game) { - if (hitOrStand.equals("y")) { + private void doHitOrStand(boolean wantToHit, BlackJackGame game) { + if (wantToHit) { handlePlayerHitProcess(game); } else { handlePlayerStandProcess(game); diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index d482849d6d3..53bb1a4ed62 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -5,5 +5,5 @@ public interface InputView { List readNames(); - String readHitOrStand(); + Boolean wantToHit(); } diff --git a/src/main/java/view/InputViewImpl.java b/src/main/java/view/InputViewImpl.java index f66b3d6ac00..8d7967a5f94 100644 --- a/src/main/java/view/InputViewImpl.java +++ b/src/main/java/view/InputViewImpl.java @@ -16,11 +16,11 @@ public List readNames() { return Arrays.stream(line.split(DELIMITER)).toList(); } - public String readHitOrStand() { + public Boolean wantToHit() { String input = sc.nextLine().trim(); validateIsBlank(input); validateHitOrStandValue(input); - return input; + return input.equals("y"); } private void validateIsBlank(String line) { diff --git a/src/test/java/controller/BlackJackControllerTest.java b/src/test/java/controller/BlackJackControllerTest.java index 52fc1485e8d..08c22109b59 100644 --- a/src/test/java/controller/BlackJackControllerTest.java +++ b/src/test/java/controller/BlackJackControllerTest.java @@ -40,7 +40,8 @@ void askPlayerNames_success() { static class TestInputViewImpl implements InputView { - private static final Deque hitOrStandOrder = new ArrayDeque<>(List.of("y", "n", "n", "n")); + private static final Deque hitOrStandOrder = new ArrayDeque<>( + List.of(Boolean.TRUE, Boolean.FALSE, Boolean.FALSE, Boolean.FALSE)); @Override public List readNames() { @@ -48,7 +49,7 @@ public List readNames() { } @Override - public String readHitOrStand() { + public Boolean wantToHit() { return hitOrStandOrder.poll(); } } From 6e79c80045bdf8a0f942511e16a34124c9a4e8c0 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Fri, 13 Mar 2026 10:41:53 +0900 Subject: [PATCH 119/129] fix : import new GameStateDto for low cohension between BlackJackController and BlackJackGame --- .../java/controller/BlackJackController.java | 8 ++------ src/main/java/domain/BlackJackGame.java | 12 +++++------ src/main/java/dto/GameStateDto.java | 13 ++++++++++++ src/main/java/view/OutputView.java | 4 ++-- src/main/java/view/OutputViewImpl.java | 11 ++++++---- .../controller/BlackJackControllerTest.java | 3 ++- src/test/java/domain/BlackJackGameTest.java | 20 ++++++++----------- 7 files changed, 40 insertions(+), 31 deletions(-) create mode 100644 src/main/java/dto/GameStateDto.java diff --git a/src/main/java/controller/BlackJackController.java b/src/main/java/controller/BlackJackController.java index e462b76d2e4..e2ec70c5e5c 100644 --- a/src/main/java/controller/BlackJackController.java +++ b/src/main/java/controller/BlackJackController.java @@ -25,10 +25,7 @@ public BlackJackController(InputView inputView, public void doGameProcess() { BlackJackGame game = retry(this::readyGame); - outputView.printInitialStates( - game.getDealerGameSettingState(), - game.getPlayersGameSettingStates() - ); + outputView.printInitialStates(game.getGameSettingState()); playPlayersTurn(game); playDealerTurn(game); @@ -93,5 +90,4 @@ private T retry(Supplier supplier) { } } } -} - +} \ No newline at end of file diff --git a/src/main/java/domain/BlackJackGame.java b/src/main/java/domain/BlackJackGame.java index cf0749a12de..ee34522cf14 100644 --- a/src/main/java/domain/BlackJackGame.java +++ b/src/main/java/domain/BlackJackGame.java @@ -4,6 +4,7 @@ import domain.state.GameState; import dto.DealerResultDto; import dto.GameResultDto; +import dto.GameStateDto; import dto.ParticipantDto; import dto.PlayerResultDto; import java.util.List; @@ -64,12 +65,11 @@ public boolean doDealerHitOrStandProcess() { return !newDealer.gameState.isFinished(); } - public List getPlayersGameSettingStates() { - return multiPlayers.getInitialStates(); - } - - public ParticipantDto getDealerGameSettingState() { - return ParticipantDto.consistWithInitialInfo(dealer); + public GameStateDto getGameSettingState() { + return GameStateDto.from( + ParticipantDto.consistWithInitialInfo(dealer), + multiPlayers.getInitialStates() + ); } public GameResultDto getGameResults() { diff --git a/src/main/java/dto/GameStateDto.java b/src/main/java/dto/GameStateDto.java new file mode 100644 index 00000000000..eced367a613 --- /dev/null +++ b/src/main/java/dto/GameStateDto.java @@ -0,0 +1,13 @@ +package dto; + +import java.util.List; + +public record GameStateDto( + ParticipantDto dealerDto, + List multiPlayersDtos +) { + + public static GameStateDto from(ParticipantDto dealerDto, List multiPlayersDtos) { + return new GameStateDto(dealerDto, multiPlayersDtos); + } +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 49a5d4b41b9..1b0675c75dd 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -1,15 +1,15 @@ package view; import dto.GameResultDto; +import dto.GameStateDto; import dto.ParticipantDto; -import java.util.List; public interface OutputView { void printErrorMessage(Exception e); void printNamePrompt(); - void printInitialStates(ParticipantDto dealerDto, List players); + void printInitialStates(GameStateDto gameStateDto); void printHitOrStandPrompt(String name); diff --git a/src/main/java/view/OutputViewImpl.java b/src/main/java/view/OutputViewImpl.java index 19c2ea81048..dcf69081bbb 100644 --- a/src/main/java/view/OutputViewImpl.java +++ b/src/main/java/view/OutputViewImpl.java @@ -4,6 +4,7 @@ import dto.CardDto; import dto.DealerResultDto; import dto.GameResultDto; +import dto.GameStateDto; import dto.ParticipantDto; import dto.PlayerResultDto; import java.util.List; @@ -29,15 +30,17 @@ public void printNamePrompt() { System.out.println(NAME_PROMPT); } - public void printInitialStates(ParticipantDto dealerDto, List players) { - String playerNames = players.stream() + @Override + public void printInitialStates(GameStateDto gameStateDto) { + List playersDtos = gameStateDto.multiPlayersDtos(); + ParticipantDto dealerDto = gameStateDto.dealerDto(); + String playerNames = playersDtos.stream() .map(ParticipantDto::name) .collect(Collectors.joining(DELIMITER)); System.out.printf(INITIAL_CARD_SHARE, playerNames); - printUserState(dealerDto); - players.forEach(this::printUserState); + playersDtos.forEach(this::printUserState); System.out.println(); } diff --git a/src/test/java/controller/BlackJackControllerTest.java b/src/test/java/controller/BlackJackControllerTest.java index 08c22109b59..74afc21c7ff 100644 --- a/src/test/java/controller/BlackJackControllerTest.java +++ b/src/test/java/controller/BlackJackControllerTest.java @@ -7,6 +7,7 @@ import domain.CardCreationStrategy; import domain.CardShape; import dto.GameResultDto; +import dto.GameStateDto; import dto.ParticipantDto; import java.util.ArrayDeque; import java.util.Deque; @@ -65,7 +66,7 @@ public void printNamePrompt() { } @Override - public void printInitialStates(ParticipantDto dealerDto, List players) { + public void printInitialStates(GameStateDto gameStateDto) { } @Override diff --git a/src/test/java/domain/BlackJackGameTest.java b/src/test/java/domain/BlackJackGameTest.java index 604980487d7..bcbf1a1e8c9 100644 --- a/src/test/java/domain/BlackJackGameTest.java +++ b/src/test/java/domain/BlackJackGameTest.java @@ -7,6 +7,7 @@ import static org.junit.jupiter.api.Assertions.assertTrue; import dto.GameResultDto; +import dto.GameStateDto; import dto.ParticipantDto; import java.util.ArrayDeque; import java.util.ArrayList; @@ -125,21 +126,16 @@ void doDealerHitOrStandProcess_return_false() { } @Test - @DisplayName("플레이어들의 게임 초기 상태를 인원 수 대로 잘 가져온다") - void getPlayersGameSettingStates_good() { - BlackJackGame testGame = BlackJackGame.ready(TEST_PLAYER_NAMES, this::createSampleCards); - - int expectSize = TEST_PLAYER_NAMES.size(); - int resultSize = testGame.getPlayersGameSettingStates().size(); - assertEquals(expectSize, resultSize); - } - - @Test - @DisplayName("딜러의 게임 초기 상태를 ParticipantDto 형태로 잘 받는다") + @DisplayName("게임 초기 상태를 GameStateDto 형태로 잘 받고, 플레이어 정보도 인원 수 만큼 존재한다") void getDealerGameSettingState_good() { BlackJackGame testGame = BlackJackGame.ready(TEST_PLAYER_NAMES, this::createSampleCards); + int expectMultiPlayersCount = TEST_PLAYER_NAMES.size(); + + GameStateDto gameSettingState = testGame.getGameSettingState(); - assertEquals(ParticipantDto.class, testGame.getDealerGameSettingState().getClass()); + int resultMultiPlayerCount = gameSettingState.multiPlayersDtos().size(); + assertEquals(GameStateDto.class, gameSettingState.getClass()); + assertEquals(expectMultiPlayersCount, resultMultiPlayerCount); } @Test From 22cd46f08a9c17c52fb301a6627118ab4d1d3ff8 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Fri, 13 Mar 2026 11:33:08 +0900 Subject: [PATCH 120/129] fix : clarify state per exact case --- src/main/java/controller/BlackJackController.java | 6 ------ src/main/java/domain/MultiPlayers.java | 2 +- src/main/java/domain/Player.java | 4 ++++ .../java/domain/state/BlackJackGameState.java | 5 +++++ src/main/java/domain/state/CommonGameState.java | 9 ++++++--- ...ameState.java => DealerPlayableGameState.java} | 8 ++++---- src/main/java/domain/state/EndGameState.java | 5 +++++ src/main/java/domain/state/GameState.java | 10 ++++++---- ...anHitGameState.java => PlayableGameState.java} | 9 +++++++-- ...ameState.java => PlayerPlayableGameState.java} | 15 ++++++++++++--- 10 files changed, 50 insertions(+), 23 deletions(-) rename src/main/java/domain/state/{DealerCanHitGameState.java => DealerPlayableGameState.java} (73%) rename src/main/java/domain/state/{CanHitGameState.java => PlayableGameState.java} (64%) rename src/main/java/domain/state/{PlayerCanHitGameState.java => PlayerPlayableGameState.java} (55%) diff --git a/src/main/java/controller/BlackJackController.java b/src/main/java/controller/BlackJackController.java index e2ec70c5e5c..9799cde1454 100644 --- a/src/main/java/controller/BlackJackController.java +++ b/src/main/java/controller/BlackJackController.java @@ -49,12 +49,6 @@ private void playDealerTurn(BlackJackGame game) { private void playPlayersTurn(BlackJackGame game) { while (game.whoseTurn().isPresent()) { Player currentPlayer = game.whoseTurn().get(); - - if (currentPlayer.isFinished()) { - handlePlayerStandProcess(game); - continue; - } - outputView.printHitOrStandPrompt(currentPlayer.getName()); boolean wantToHit = retry(inputView::wantToHit); doHitOrStand(wantToHit, game); diff --git a/src/main/java/domain/MultiPlayers.java b/src/main/java/domain/MultiPlayers.java index fb78bcf9ab3..6ba5b3a2b3c 100644 --- a/src/main/java/domain/MultiPlayers.java +++ b/src/main/java/domain/MultiPlayers.java @@ -57,7 +57,7 @@ private static void validateUserCountLimit(List playerNames) { public Optional findNotStayPlayer() { return players.stream() - .filter(player -> !player.isFinished()) + .filter(Player::isPlayable) .findFirst(); } diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java index a42adc58f9c..1ebaabe88da 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/Player.java @@ -30,6 +30,10 @@ public Player stand() { ); } + public boolean isPlayable() { + return gameState.isPlayable(); + } + public boolean isFinished() { return gameState.isFinished(); } diff --git a/src/main/java/domain/state/BlackJackGameState.java b/src/main/java/domain/state/BlackJackGameState.java index ad1daa32d6b..633856fe827 100644 --- a/src/main/java/domain/state/BlackJackGameState.java +++ b/src/main/java/domain/state/BlackJackGameState.java @@ -6,4 +6,9 @@ public class BlackJackGameState extends EndGameState { public BlackJackGameState(Hand hand) { super(hand); } + + @Override + public boolean isPlayable() { + return false; + } } \ No newline at end of file diff --git a/src/main/java/domain/state/CommonGameState.java b/src/main/java/domain/state/CommonGameState.java index 3e5c4643b28..064fbae156d 100644 --- a/src/main/java/domain/state/CommonGameState.java +++ b/src/main/java/domain/state/CommonGameState.java @@ -19,6 +19,12 @@ public CommonGameState(Hand hand) { @Override public abstract GameState stay(); + @Override + public abstract boolean isPlayable(); + + @Override + public abstract boolean isFinished(); + @Override public boolean isBlackJack() { return hand.isBlackJack(); @@ -29,9 +35,6 @@ public boolean isBust() { return hand.isBust(); } - @Override - public abstract boolean isFinished(); - @Override public List showOwnCards() { return hand.showCards(); diff --git a/src/main/java/domain/state/DealerCanHitGameState.java b/src/main/java/domain/state/DealerPlayableGameState.java similarity index 73% rename from src/main/java/domain/state/DealerCanHitGameState.java rename to src/main/java/domain/state/DealerPlayableGameState.java index 396ef39188b..e2c06f51d2a 100644 --- a/src/main/java/domain/state/DealerCanHitGameState.java +++ b/src/main/java/domain/state/DealerPlayableGameState.java @@ -4,10 +4,10 @@ import domain.Hand; import java.util.function.Supplier; -public class DealerCanHitGameState extends CanHitGameState { +public class DealerPlayableGameState extends PlayableGameState { private static final int MINIMUM_TOTAL_SCORE = 16; - public DealerCanHitGameState(Hand hand) { + public DealerPlayableGameState(Hand hand) { super(hand); } @@ -18,11 +18,11 @@ public GameState hit(Supplier cardSupplier) { return new BustGameState(newHand); } - if (newHand.calculateCardScoreSum() > MINIMUM_TOTAL_SCORE) { + if (newHand.calculateCardScoreSum() >= MINIMUM_TOTAL_SCORE) { return new StayGameState(newHand); } - return new DealerCanHitGameState(newHand); + return new DealerPlayableGameState(newHand); } @Override diff --git a/src/main/java/domain/state/EndGameState.java b/src/main/java/domain/state/EndGameState.java index 8034c42ad9b..31d76b8a29a 100644 --- a/src/main/java/domain/state/EndGameState.java +++ b/src/main/java/domain/state/EndGameState.java @@ -24,4 +24,9 @@ public GameState stay() { public boolean isFinished() { return true; } + + @Override + public boolean isPlayable() { + return false; + } } diff --git a/src/main/java/domain/state/GameState.java b/src/main/java/domain/state/GameState.java index 56c3c3d0d07..6361fe51063 100644 --- a/src/main/java/domain/state/GameState.java +++ b/src/main/java/domain/state/GameState.java @@ -11,26 +11,28 @@ static GameState createPlayerInitialGameState(Hand hand) { if (hand.isBlackJack()) { return new BlackJackGameState(hand); } - return new PlayerCanHitGameState(hand); + return new PlayerPlayableGameState(hand); } static GameState createDealerInitialGameState(Hand hand) { if (hand.isBlackJack()) { return new BlackJackGameState(hand); } - return new DealerCanHitGameState(hand); + return new DealerPlayableGameState(hand); } GameState hit(Supplier cardSupplier); GameState stay(); + boolean isPlayable(); + + boolean isFinished(); + boolean isBlackJack(); boolean isBust(); - boolean isFinished(); - List showOwnCards(); int getCardsSum(); diff --git a/src/main/java/domain/state/CanHitGameState.java b/src/main/java/domain/state/PlayableGameState.java similarity index 64% rename from src/main/java/domain/state/CanHitGameState.java rename to src/main/java/domain/state/PlayableGameState.java index 7ee31d331e1..cc053b76ff5 100644 --- a/src/main/java/domain/state/CanHitGameState.java +++ b/src/main/java/domain/state/PlayableGameState.java @@ -4,9 +4,9 @@ import domain.Hand; import java.util.function.Supplier; -public abstract class CanHitGameState extends CommonGameState { +public abstract class PlayableGameState extends CommonGameState { - public CanHitGameState(Hand hand) { + public PlayableGameState(Hand hand) { super(hand); } @@ -16,6 +16,11 @@ public CanHitGameState(Hand hand) { @Override public abstract GameState stay(); + @Override + public boolean isPlayable() { + return true; + } + @Override public boolean isFinished() { return false; diff --git a/src/main/java/domain/state/PlayerCanHitGameState.java b/src/main/java/domain/state/PlayerPlayableGameState.java similarity index 55% rename from src/main/java/domain/state/PlayerCanHitGameState.java rename to src/main/java/domain/state/PlayerPlayableGameState.java index 9d731953e68..ccf1ccaaa97 100644 --- a/src/main/java/domain/state/PlayerCanHitGameState.java +++ b/src/main/java/domain/state/PlayerPlayableGameState.java @@ -4,8 +4,8 @@ import domain.Hand; import java.util.function.Supplier; -public class PlayerCanHitGameState extends CanHitGameState { - public PlayerCanHitGameState(Hand hand) { +public class PlayerPlayableGameState extends PlayableGameState { + public PlayerPlayableGameState(Hand hand) { super(hand); } @@ -15,7 +15,16 @@ public GameState hit(Supplier cardSupplier) { if (newHand.isBust()) { return new BustGameState(newHand); } - return new PlayerCanHitGameState(newHand); + + if (newHand.isBlackJack()) { + return new BlackJackGameState(newHand); + } + + if (newHand.isFull()) { + return new StayGameState(newHand); + } + + return new PlayerPlayableGameState(newHand); } @Override From 363e1705036c467f2b122596cce53bb6d302085c Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Fri, 13 Mar 2026 13:34:48 +0900 Subject: [PATCH 121/129] fix : change list to map MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 찾기 오버헤드를 줄이기 위해 변경. 현재는 n=5라는 조건 때문에 찾기 시 성능을 고려하지 않았으나, 이 또한 변경가능한 요구사항이기 때문에 변경하기로 결정함 --- src/main/java/domain/MultiPlayers.java | 53 +++++++++++++------------- src/main/java/domain/Participant.java | 8 ---- 2 files changed, 26 insertions(+), 35 deletions(-) diff --git a/src/main/java/domain/MultiPlayers.java b/src/main/java/domain/MultiPlayers.java index 6ba5b3a2b3c..a5303acfac4 100644 --- a/src/main/java/domain/MultiPlayers.java +++ b/src/main/java/domain/MultiPlayers.java @@ -4,8 +4,9 @@ import domain.state.GameState; import dto.ParticipantDto; import dto.PlayerResultDto; -import java.util.ArrayList; +import java.util.LinkedHashMap; import java.util.List; +import java.util.Map; import java.util.Optional; import java.util.function.Supplier; import java.util.function.UnaryOperator; @@ -13,25 +14,22 @@ public class MultiPlayers { private static final int MAX_PLAYER_NUMBER = 5; - private final List players; + private final Map players; - private MultiPlayers(List players) { + private MultiPlayers(Map players) { this.players = players; } public static MultiPlayers of(List playerNames, Deck totalDeck) { - return new MultiPlayers( - new ArrayList<>(createPlayers(playerNames, totalDeck)) - ); - } - - private static List createPlayers(List playerNames, Deck totalDeck) { validateUserCountLimit(playerNames); validateNameUniqueness(playerNames); - return playerNames.stream() - .map(name -> createNewPlayer(totalDeck, name)) - .toList(); + Map newPlayers = new LinkedHashMap<>(); + for (String name : playerNames) { + newPlayers.put(name, createNewPlayer(totalDeck, name)); + } + + return new MultiPlayers(newPlayers); } private static Player createNewPlayer(Deck totalDeck, String name) { @@ -39,7 +37,8 @@ private static Player createNewPlayer(Deck totalDeck, String name) { Hand newPlayerHand = Hand.of(twoCards.get(0), twoCards.get(1)); return Player.from( name, - GameState.createPlayerInitialGameState(newPlayerHand)); + GameState.createPlayerInitialGameState(newPlayerHand) + ); } private static void validateNameUniqueness(List playerNames) { @@ -56,39 +55,39 @@ private static void validateUserCountLimit(List playerNames) { } public Optional findNotStayPlayer() { - return players.stream() + return players.values().stream() .filter(Player::isPlayable) .findFirst(); } public Player executeHit(Player player, Supplier cardSupplier) { - return applyAction(player, p -> p.hit(cardSupplier)); + return applyAction(player.getName(), p -> p.hit(cardSupplier)); } public Player executeStand(Player player) { - return applyAction(player, Player::stand); + return applyAction(player.getName(), Player::stand); } public List getInitialStates() { - return players.stream() + return players.values().stream() .map(ParticipantDto::consistWithInitialInfo) .toList(); } public List checkPlayersGameResult(Dealer dealer) { - return players.stream().map( - player -> PlayerResultDto.from(player, dealer.gameState) - ).toList(); + return players.values().stream() + .map(player -> PlayerResultDto.from(player, dealer.gameState)) + .toList(); } - private Player applyAction(Player player, UnaryOperator action) { - int index = players.indexOf(player); - if (index == -1) { + private Player applyAction(String name, UnaryOperator action) { + Player player = players.get(name); + if (player == null) { throw new IllegalArgumentException(ErrorMessage.PLAYER_NOT_FOUND.getMessage()); } - Player newPlayer = action.apply(player); - players.set(index, newPlayer); - return newPlayer; + Player updatedPlayer = action.apply(player); + players.put(name, updatedPlayer); + return updatedPlayer; } -} +} \ No newline at end of file diff --git a/src/main/java/domain/Participant.java b/src/main/java/domain/Participant.java index df54ad2b6e8..1286f24bc96 100644 --- a/src/main/java/domain/Participant.java +++ b/src/main/java/domain/Participant.java @@ -25,12 +25,4 @@ public List showOwnCards() { public int getOwnCardsSum() { return this.gameState.getCardsSum(); } -// -// public boolean isBust() { -// return gameState.is(); -// } -// -// public boolean isBlackJack() { -// return hand.isBlackJack(); -// } } \ No newline at end of file From 7f62ae026a138bcbfac635bbb0e061a7263a7acf Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Fri, 13 Mar 2026 17:49:07 +0900 Subject: [PATCH 122/129] fix : refactor game states MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 복잡한 관계를 가진 game state 구조 정리 --- .../java/domain/state/BlackJackGameState.java | 7 +-- src/main/java/domain/state/BustGameState.java | 2 +- .../java/domain/state/CommonGameState.java | 52 ------------------- .../domain/state/DealerPlayableGameState.java | 35 ------------- ...dGameState.java => FinishedGameState.java} | 4 +- src/main/java/domain/state/GameState.java | 14 +---- .../java/domain/state/PlayableGameState.java | 28 ---------- ...leGameState.java => RunningGameState.java} | 17 ++++-- .../java/domain/state/StartedGameState.java | 33 ++++++++++++ src/main/java/domain/state/StayGameState.java | 2 +- 10 files changed, 54 insertions(+), 140 deletions(-) delete mode 100644 src/main/java/domain/state/CommonGameState.java delete mode 100644 src/main/java/domain/state/DealerPlayableGameState.java rename src/main/java/domain/state/{EndGameState.java => FinishedGameState.java} (84%) delete mode 100644 src/main/java/domain/state/PlayableGameState.java rename src/main/java/domain/state/{PlayerPlayableGameState.java => RunningGameState.java} (68%) create mode 100644 src/main/java/domain/state/StartedGameState.java diff --git a/src/main/java/domain/state/BlackJackGameState.java b/src/main/java/domain/state/BlackJackGameState.java index 633856fe827..c102e304d0d 100644 --- a/src/main/java/domain/state/BlackJackGameState.java +++ b/src/main/java/domain/state/BlackJackGameState.java @@ -2,13 +2,8 @@ import domain.Hand; -public class BlackJackGameState extends EndGameState { +public class BlackJackGameState extends FinishedGameState { public BlackJackGameState(Hand hand) { super(hand); } - - @Override - public boolean isPlayable() { - return false; - } } \ No newline at end of file diff --git a/src/main/java/domain/state/BustGameState.java b/src/main/java/domain/state/BustGameState.java index 3c3ee63ddd6..c30a79eac06 100644 --- a/src/main/java/domain/state/BustGameState.java +++ b/src/main/java/domain/state/BustGameState.java @@ -2,7 +2,7 @@ import domain.Hand; -public class BustGameState extends EndGameState { +public class BustGameState extends FinishedGameState { public BustGameState(Hand hand) { super(hand); } diff --git a/src/main/java/domain/state/CommonGameState.java b/src/main/java/domain/state/CommonGameState.java deleted file mode 100644 index 064fbae156d..00000000000 --- a/src/main/java/domain/state/CommonGameState.java +++ /dev/null @@ -1,52 +0,0 @@ -package domain.state; - -import domain.Card; -import domain.GameResult; -import domain.Hand; -import java.util.List; -import java.util.function.Supplier; - -public abstract class CommonGameState implements GameState { - protected final Hand hand; - - public CommonGameState(Hand hand) { - this.hand = hand; - } - - @Override - public abstract GameState hit(Supplier cardSupplier); - - @Override - public abstract GameState stay(); - - @Override - public abstract boolean isPlayable(); - - @Override - public abstract boolean isFinished(); - - @Override - public boolean isBlackJack() { - return hand.isBlackJack(); - } - - @Override - public boolean isBust() { - return hand.isBust(); - } - - @Override - public List showOwnCards() { - return hand.showCards(); - } - - @Override - public int getCardsSum() { - return hand.calculateCardScoreSum(); - } - - @Override - public GameResult compare(GameState playerState, GameState dealerState) { - return GameResult.decidePlayerResult(playerState, dealerState); - } -} diff --git a/src/main/java/domain/state/DealerPlayableGameState.java b/src/main/java/domain/state/DealerPlayableGameState.java deleted file mode 100644 index e2c06f51d2a..00000000000 --- a/src/main/java/domain/state/DealerPlayableGameState.java +++ /dev/null @@ -1,35 +0,0 @@ -package domain.state; - -import domain.Card; -import domain.Hand; -import java.util.function.Supplier; - -public class DealerPlayableGameState extends PlayableGameState { - private static final int MINIMUM_TOTAL_SCORE = 16; - - public DealerPlayableGameState(Hand hand) { - super(hand); - } - - @Override - public GameState hit(Supplier cardSupplier) { - Hand newHand = this.hand.addCard(cardSupplier.get()); - if (newHand.isBust()) { - return new BustGameState(newHand); - } - - if (newHand.calculateCardScoreSum() >= MINIMUM_TOTAL_SCORE) { - return new StayGameState(newHand); - } - - return new DealerPlayableGameState(newHand); - } - - @Override - public GameState stay() { - if (hand.calculateCardScoreSum() > MINIMUM_TOTAL_SCORE) { - return new StayGameState(hand); - } - return this; - } -} diff --git a/src/main/java/domain/state/EndGameState.java b/src/main/java/domain/state/FinishedGameState.java similarity index 84% rename from src/main/java/domain/state/EndGameState.java rename to src/main/java/domain/state/FinishedGameState.java index 31d76b8a29a..498ef7a9453 100644 --- a/src/main/java/domain/state/EndGameState.java +++ b/src/main/java/domain/state/FinishedGameState.java @@ -5,8 +5,8 @@ import domain.Hand; import java.util.function.Supplier; -public abstract class EndGameState extends CommonGameState { - public EndGameState(Hand hand) { +public abstract class FinishedGameState extends StartedGameState { + protected FinishedGameState(Hand hand) { super(hand); } diff --git a/src/main/java/domain/state/GameState.java b/src/main/java/domain/state/GameState.java index 6361fe51063..d24dc9124e0 100644 --- a/src/main/java/domain/state/GameState.java +++ b/src/main/java/domain/state/GameState.java @@ -1,24 +1,16 @@ package domain.state; import domain.Card; -import domain.GameResult; import domain.Hand; import java.util.List; import java.util.function.Supplier; public interface GameState { - static GameState createPlayerInitialGameState(Hand hand) { + static GameState createInitialGameState(Hand hand) { if (hand.isBlackJack()) { return new BlackJackGameState(hand); } - return new PlayerPlayableGameState(hand); - } - - static GameState createDealerInitialGameState(Hand hand) { - if (hand.isBlackJack()) { - return new BlackJackGameState(hand); - } - return new DealerPlayableGameState(hand); + return new RunningGameState(hand); } GameState hit(Supplier cardSupplier); @@ -36,6 +28,4 @@ static GameState createDealerInitialGameState(Hand hand) { List showOwnCards(); int getCardsSum(); - - GameResult compare(GameState playerState, GameState dealerState); } \ No newline at end of file diff --git a/src/main/java/domain/state/PlayableGameState.java b/src/main/java/domain/state/PlayableGameState.java deleted file mode 100644 index cc053b76ff5..00000000000 --- a/src/main/java/domain/state/PlayableGameState.java +++ /dev/null @@ -1,28 +0,0 @@ -package domain.state; - -import domain.Card; -import domain.Hand; -import java.util.function.Supplier; - -public abstract class PlayableGameState extends CommonGameState { - - public PlayableGameState(Hand hand) { - super(hand); - } - - @Override - public abstract GameState hit(Supplier cardSupplier); - - @Override - public abstract GameState stay(); - - @Override - public boolean isPlayable() { - return true; - } - - @Override - public boolean isFinished() { - return false; - } -} \ No newline at end of file diff --git a/src/main/java/domain/state/PlayerPlayableGameState.java b/src/main/java/domain/state/RunningGameState.java similarity index 68% rename from src/main/java/domain/state/PlayerPlayableGameState.java rename to src/main/java/domain/state/RunningGameState.java index ccf1ccaaa97..5ed72a49804 100644 --- a/src/main/java/domain/state/PlayerPlayableGameState.java +++ b/src/main/java/domain/state/RunningGameState.java @@ -4,8 +4,9 @@ import domain.Hand; import java.util.function.Supplier; -public class PlayerPlayableGameState extends PlayableGameState { - public PlayerPlayableGameState(Hand hand) { +public class RunningGameState extends StartedGameState { + + public RunningGameState(Hand hand) { super(hand); } @@ -24,11 +25,21 @@ public GameState hit(Supplier cardSupplier) { return new StayGameState(newHand); } - return new PlayerPlayableGameState(newHand); + return new RunningGameState(newHand); } @Override public GameState stay() { return new StayGameState(hand); } + + @Override + public boolean isPlayable() { + return true; + } + + @Override + public boolean isFinished() { + return false; + } } diff --git a/src/main/java/domain/state/StartedGameState.java b/src/main/java/domain/state/StartedGameState.java new file mode 100644 index 00000000000..d8ce744407a --- /dev/null +++ b/src/main/java/domain/state/StartedGameState.java @@ -0,0 +1,33 @@ +package domain.state; + +import domain.Card; +import domain.Hand; +import java.util.List; + +public abstract class StartedGameState implements GameState { + protected final Hand hand; + + protected StartedGameState(Hand hand) { + this.hand = hand; + } + + @Override + public boolean isBlackJack() { + return hand.isBlackJack(); + } + + @Override + public boolean isBust() { + return hand.isBust(); + } + + @Override + public List showOwnCards() { + return hand.showCards(); + } + + @Override + public int getCardsSum() { + return hand.calculateCardScoreSum(); + } +} diff --git a/src/main/java/domain/state/StayGameState.java b/src/main/java/domain/state/StayGameState.java index 4b1daee6551..f705ffe0857 100644 --- a/src/main/java/domain/state/StayGameState.java +++ b/src/main/java/domain/state/StayGameState.java @@ -2,7 +2,7 @@ import domain.Hand; -public class StayGameState extends EndGameState { +public class StayGameState extends FinishedGameState { public StayGameState(Hand hand) { super(hand); } From ee583057352e5c2c9a42528d643fed7bf5ffedf5 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Fri, 13 Mar 2026 18:28:59 +0900 Subject: [PATCH 123/129] feat : add BetAmount Domain --- src/main/java/common/ErrorMessage.java | 1 + src/main/java/domain/BetAmount.java | 19 ++++++++++ src/main/java/domain/BlackJackGame.java | 2 +- src/main/java/domain/Dealer.java | 13 +++++-- src/main/java/domain/GameResult.java | 5 ++- src/main/java/domain/MultiPlayers.java | 4 +- src/main/java/domain/Participant.java | 9 ++++- src/main/java/domain/Player.java | 24 ++++++++---- src/main/java/dto/PlayerResultDto.java | 6 +-- src/test/java/domain/DealerTest.java | 4 +- src/test/java/domain/GameResultTest.java | 44 +++++++++++----------- src/test/java/domain/MultiPlayersTest.java | 8 ++-- src/test/java/domain/PlayerTest.java | 18 ++++----- 13 files changed, 101 insertions(+), 56 deletions(-) create mode 100644 src/main/java/domain/BetAmount.java diff --git a/src/main/java/common/ErrorMessage.java b/src/main/java/common/ErrorMessage.java index 5271d81586d..8f6b0121c65 100644 --- a/src/main/java/common/ErrorMessage.java +++ b/src/main/java/common/ErrorMessage.java @@ -10,6 +10,7 @@ public enum ErrorMessage { PLAYER_NOT_FOUND("해당 플레이어를 찾을 수 없습니다"), NO_MORE_PLAYABLE_PLAYER("더 이상 게임을 진행할 수 있는 플레이어가 없습니다."), NOT_ALLOW_METHOD_CALL("현재 상태에서 허용되지 않는 메소드 호출입니다"), + ZERO_MINUS_MONEY("0원 이하 금액을 베팅할 수는 없습니다"), MAX_PLAYER_ERROR("최대 인원을 초과했습니다."); private final String message; diff --git a/src/main/java/domain/BetAmount.java b/src/main/java/domain/BetAmount.java new file mode 100644 index 00000000000..fa22b0b01e4 --- /dev/null +++ b/src/main/java/domain/BetAmount.java @@ -0,0 +1,19 @@ +package domain; + +import common.ErrorMessage; + +public record BetAmount( + int betAmount +) { + public static BetAmount of(int betAmount) { + if (betAmount <= 0) { + throw new IllegalArgumentException(ErrorMessage.ZERO_MINUS_MONEY.getMessage()); + } + return new BetAmount(betAmount); + } + + public static BetAmount empty() { + return new BetAmount(0); + } +} + diff --git a/src/main/java/domain/BlackJackGame.java b/src/main/java/domain/BlackJackGame.java index ee34522cf14..b10c0d8c7fe 100644 --- a/src/main/java/domain/BlackJackGame.java +++ b/src/main/java/domain/BlackJackGame.java @@ -37,7 +37,7 @@ private static Dealer createNewDealer(Deck totalDeck) { dealersInitialCards.get(1) ); return Dealer.from( - GameState.createDealerInitialGameState(initialDealerHand) + GameState.createInitialGameState(initialDealerHand) ); } diff --git a/src/main/java/domain/Dealer.java b/src/main/java/domain/Dealer.java index 311bd4cdd08..089fbd15873 100644 --- a/src/main/java/domain/Dealer.java +++ b/src/main/java/domain/Dealer.java @@ -7,12 +7,19 @@ public class Dealer extends Participant { private static final String DEALER_NAME = "딜러"; - private Dealer(GameState gameState) { - super(DEALER_NAME, gameState); + private Dealer(GameState gameState, BetAmount betAmount) { + super(DEALER_NAME, gameState, betAmount); } public static Dealer from(GameState gameState) { - return new Dealer(gameState); + return new Dealer(gameState, BetAmount.empty()); + } + + public Dealer bet(int amount) { + return new Dealer( + this.gameState, + BetAmount.of(amount) + ); } @Override diff --git a/src/main/java/domain/GameResult.java b/src/main/java/domain/GameResult.java index e349546e6a5..b619fbe9cec 100644 --- a/src/main/java/domain/GameResult.java +++ b/src/main/java/domain/GameResult.java @@ -5,7 +5,10 @@ public enum GameResult { 승, 무, 패; - public static GameResult decidePlayerResult(GameState playerGameState, GameState dealerGameState) { + public static GameResult decidePlayerResult(Player player, Dealer dealer) { + GameState playerGameState = player.gameState; + GameState dealerGameState = dealer.gameState; + if (playerGameState.isBust()) { return GameResult.패; } diff --git a/src/main/java/domain/MultiPlayers.java b/src/main/java/domain/MultiPlayers.java index a5303acfac4..f39508e3aac 100644 --- a/src/main/java/domain/MultiPlayers.java +++ b/src/main/java/domain/MultiPlayers.java @@ -37,7 +37,7 @@ private static Player createNewPlayer(Deck totalDeck, String name) { Hand newPlayerHand = Hand.of(twoCards.get(0), twoCards.get(1)); return Player.from( name, - GameState.createPlayerInitialGameState(newPlayerHand) + GameState.createInitialGameState(newPlayerHand) ); } @@ -76,7 +76,7 @@ public List getInitialStates() { public List checkPlayersGameResult(Dealer dealer) { return players.values().stream() - .map(player -> PlayerResultDto.from(player, dealer.gameState)) + .map(player -> PlayerResultDto.from(player, dealer)) .toList(); } diff --git a/src/main/java/domain/Participant.java b/src/main/java/domain/Participant.java index 1286f24bc96..dac7943ab45 100644 --- a/src/main/java/domain/Participant.java +++ b/src/main/java/domain/Participant.java @@ -2,14 +2,17 @@ import domain.state.GameState; import java.util.List; +import java.util.Objects; public abstract class Participant { protected final GameState gameState; protected final ParticipantName participantName; + protected final BetAmount betAmount; - protected Participant(String name, GameState gameState) { + protected Participant(String name, GameState gameState, BetAmount betAmount) { this.participantName = new ParticipantName(name); this.gameState = gameState; + this.betAmount = betAmount; } public abstract List showInitialCard(); @@ -18,6 +21,10 @@ public String getName() { return participantName.name(); } + public boolean isBet() { + return !Objects.equals(betAmount, BetAmount.empty()); + } + public List showOwnCards() { return gameState.showOwnCards(); } diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java index 1ebaabe88da..680caef9a25 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/Player.java @@ -6,19 +6,28 @@ import java.util.function.Supplier; public class Player extends Participant { - private Player(String name, GameState gameState) { - super(name, gameState); + private Player(String name, GameState gameState, BetAmount betAmount) { + super(name, gameState, betAmount); } public static Player from(String name, GameState gameState) { - return new Player(name, gameState); + return new Player(name, gameState, BetAmount.empty()); + } + + public Player bet(int amount) { + return new Player( + this.participantName.name(), + this.gameState, + BetAmount.of(amount) + ); } public Player hit(Supplier cardSupplier) { GameState newGameState = gameState.hit(cardSupplier); return new Player( this.participantName.name(), - newGameState + newGameState, + this.betAmount ); } @@ -26,7 +35,8 @@ public Player stand() { GameState newStandGameState = gameState.stay(); return new Player( this.participantName.name(), - newStandGameState + newStandGameState, + this.betAmount ); } @@ -38,8 +48,8 @@ public boolean isFinished() { return gameState.isFinished(); } - public GameResult calculateGameResult(GameState dealerGameState) { - return gameState.compare(gameState, dealerGameState); + public GameResult calculateGameResult(Dealer dealer) { + return GameResult.decidePlayerResult(this, dealer); } @Override diff --git a/src/main/java/dto/PlayerResultDto.java b/src/main/java/dto/PlayerResultDto.java index 54e790762ed..ada00f9555b 100644 --- a/src/main/java/dto/PlayerResultDto.java +++ b/src/main/java/dto/PlayerResultDto.java @@ -1,19 +1,19 @@ package dto; +import domain.Dealer; import domain.GameResult; import domain.Player; -import domain.state.GameState; public record PlayerResultDto( ParticipantDto playerDto, int score, GameResult result ) { - public static PlayerResultDto from(Player player, GameState dealerGameState) { + public static PlayerResultDto from(Player player, Dealer dealer) { return new PlayerResultDto( ParticipantDto.from(player), player.getOwnCardsSum(), - player.calculateGameResult(dealerGameState) + player.calculateGameResult(dealer) ); } } diff --git a/src/test/java/domain/DealerTest.java b/src/test/java/domain/DealerTest.java index a07d396958c..911c97b04f1 100644 --- a/src/test/java/domain/DealerTest.java +++ b/src/test/java/domain/DealerTest.java @@ -14,7 +14,7 @@ public class DealerTest { @Test @DisplayName("Dealer를 생성할 때 오류 발생 안함") void dealer_create_success() { - GameState dealerGameState = GameState.createDealerInitialGameState( + GameState dealerGameState = GameState.createInitialGameState( Hand.of( new Card(CardShape.스페이드, CardContents.J), new Card(CardShape.클로버, CardContents.FIVE) @@ -34,7 +34,7 @@ void addCard_success() { }; Deck totalDeck = Deck.createDeck(onlyJCardCreation); - GameState dealerGameState = GameState.createDealerInitialGameState( + GameState dealerGameState = GameState.createInitialGameState( Hand.of( new Card(CardShape.하트, CardContents.SIX), new Card(CardShape.스페이드, CardContents.TEN) diff --git a/src/test/java/domain/GameResultTest.java b/src/test/java/domain/GameResultTest.java index 4a740c0d132..be8d66a20a7 100644 --- a/src/test/java/domain/GameResultTest.java +++ b/src/test/java/domain/GameResultTest.java @@ -12,6 +12,9 @@ import org.junit.jupiter.api.Test; class GameResultTest { + + private static final String GUMP = "gump"; + Hand normalHand = Hand.of( new Card(CardShape.다이아몬드, CardContents.A), new Card(CardShape.다이아몬드, CardContents.TWO) @@ -34,11 +37,11 @@ class blackJackCaseTest { @DisplayName("플레이어가 블랙잭이면 플레이어가 이긴 결과를 도출한다") void decidePlayerResult_player_blackjack() { //given - GameState dealerInitialGameState = GameState.createDealerInitialGameState(normalHand); - GameState playerInitialGameState = GameState.createPlayerInitialGameState(blackJackHand); + Dealer testDealer = Dealer.from(GameState.createInitialGameState(normalHand)); + Player testPlayer = Player.from(GUMP, GameState.createInitialGameState(blackJackHand)); //when - GameResult result = GameResult.decidePlayerResult(playerInitialGameState, dealerInitialGameState); + GameResult result = GameResult.decidePlayerResult(testPlayer, testDealer); //then assertEquals(GameResult.승, result); @@ -48,11 +51,11 @@ void decidePlayerResult_player_blackjack() { @DisplayName("딜러가 블랙잭이면 플레이어가 진 결과를 도출한다") void decidePlayerResult_dealer_blackjack() { //given - GameState dealerGameState = GameState.createDealerInitialGameState(blackJackHand); - GameState playerGameState = GameState.createPlayerInitialGameState(normalHand); + Dealer testDealer = Dealer.from(GameState.createInitialGameState(blackJackHand)); + Player testPlayer = Player.from(GUMP, GameState.createInitialGameState(normalHand)); //when - GameResult result = GameResult.decidePlayerResult(playerGameState, dealerGameState); + GameResult result = GameResult.decidePlayerResult(testPlayer, testDealer); //then assertEquals(GameResult.패, result); @@ -67,11 +70,11 @@ void decidePlayerResult_both_blackjack() { new Card(CardShape.하트, CardContents.TEN) ); - GameState dealerGameState = GameState.createDealerInitialGameState(blackJackHand); - GameState playerGameState = GameState.createPlayerInitialGameState(anotherBlackJackHand); + Dealer testDealer = Dealer.from(GameState.createInitialGameState(blackJackHand)); + Player testPlayer = Player.from(GUMP, GameState.createInitialGameState(anotherBlackJackHand)); //when - GameResult result = GameResult.decidePlayerResult(playerGameState, dealerGameState); + GameResult result = GameResult.decidePlayerResult(testPlayer, testDealer); //then assertEquals(GameResult.무, result); @@ -93,16 +96,13 @@ class bustCaseTest { @DisplayName("플레이어가 bust 이면 패 결과를 반환") void player_bust_case() { //given - GameState dealerGameState = GameState.createDealerInitialGameState(normalHand); - GameState playerGameState = GameState.createPlayerInitialGameState(handThatValue16); - Player testPlayer = Player.from( - "gump", - playerGameState - ); + + Player testPlayer = Player.from(GUMP, GameState.createInitialGameState(handThatValue16)); + Dealer testDealer = Dealer.from(GameState.createInitialGameState(normalHand)); testPlayer = testPlayer.hit(testTotalDeck::drawCard); //when - GameResult result = GameResult.decidePlayerResult(testPlayer.gameState, dealerGameState); + GameResult result = GameResult.decidePlayerResult(testPlayer, testDealer); //then assertEquals(GameResult.패, result); @@ -112,15 +112,13 @@ void player_bust_case() { @DisplayName("딜러가 bust 이면 승 결과를 반환") void dealer_bust_case() { //given - GameState dealerGameState = GameState.createDealerInitialGameState(handThatValue16); - GameState playerGameState = GameState.createPlayerInitialGameState(normalHand); - Dealer testDealer = Dealer.from( - dealerGameState - ); - Dealer newDealer = testDealer.addCard(testTotalDeck::drawCard); + Player testPlayer = Player.from(GUMP, GameState.createInitialGameState(normalHand)); + Dealer testDealer = Dealer.from(GameState.createInitialGameState(handThatValue16)); + + testDealer = testDealer.addCard(testTotalDeck::drawCard); //when - GameResult result = GameResult.decidePlayerResult(playerGameState, newDealer.gameState); + GameResult result = GameResult.decidePlayerResult(testPlayer, testDealer); //then assertEquals(GameResult.승, result); diff --git a/src/test/java/domain/MultiPlayersTest.java b/src/test/java/domain/MultiPlayersTest.java index 70e3c28978a..a0ff23b648a 100644 --- a/src/test/java/domain/MultiPlayersTest.java +++ b/src/test/java/domain/MultiPlayersTest.java @@ -151,7 +151,7 @@ void getInitialState_success() { ParticipantDto expect = ParticipantDto.from( Player.from( testerName, - GameState.createPlayerInitialGameState(Hand.of(heartTen, heartJ)) + GameState.createInitialGameState(Hand.of(heartTen, heartJ)) ) ); @@ -176,15 +176,15 @@ void checkPlayersGameResult_success() { Queue onlyTwoTenCards = new LinkedList<>(List.of(heartTen, heartJ)); Dealer testDealer = Dealer.from( - GameState.createDealerInitialGameState(Hand.of(heartTwo, heartThree)) + GameState.createInitialGameState(Hand.of(heartTwo, heartThree)) ); Player expectedPlayer = Player.from( testerName, - GameState.createPlayerInitialGameState(Hand.of(heartTen, heartJ)) + GameState.createInitialGameState(Hand.of(heartTen, heartJ)) ); CardCreationStrategy onlyTwoTenCardsCreationStrategy = () -> new ArrayDeque<>(onlyTwoTenCards); MultiPlayers multiPlayers = MultiPlayers.of(onlyOneNames, Deck.createDeck(onlyTwoTenCardsCreationStrategy)); - PlayerResultDto expect = PlayerResultDto.from(expectedPlayer, testDealer.gameState); + PlayerResultDto expect = PlayerResultDto.from(expectedPlayer, testDealer); //when List result = multiPlayers.checkPlayersGameResult(testDealer); diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java index 049f658fc47..fff916fabeb 100644 --- a/src/test/java/domain/PlayerTest.java +++ b/src/test/java/domain/PlayerTest.java @@ -27,7 +27,7 @@ void player_create_success() { String name = "pobi"; assertDoesNotThrow( - () -> Player.from(name, GameState.createPlayerInitialGameState(playerHand)) + () -> Player.from(name, GameState.createInitialGameState(playerHand)) ); } @@ -48,7 +48,7 @@ class hitTest { String testName = "gump"; Player testPlayerWhoHoldTotal15Cards = Player.from( testName, - GameState.createPlayerInitialGameState(playerHand) + GameState.createInitialGameState(playerHand) ); @Test @@ -91,7 +91,7 @@ void hit_butBust_and_finish() { String testName = "gump"; Player testPlayer = Player.from( testName, - GameState.createPlayerInitialGameState(playerHand) + GameState.createInitialGameState(playerHand) ); testPlayer.hit(onlyTwoTenCardSupplier); @@ -114,7 +114,7 @@ void stand_and_finish() { String testName = "gump"; Player testPlayer = Player.from( testName, - GameState.createPlayerInitialGameState(playerHand) + GameState.createInitialGameState(playerHand) ); //when @@ -137,14 +137,14 @@ void lose_when_dealer_blackjack() { new Card(CardShape.스페이드, CardContents.TWO), new Card(CardShape.클로버, CardContents.THREE) ); - GameState dealerGameState = GameState.createDealerInitialGameState(blackJackHand); + Dealer testDealer = Dealer.from(GameState.createInitialGameState(blackJackHand)); Player testPlayer = Player.from( testPlayerName, - GameState.createPlayerInitialGameState(notBlackJackAndNotBustHand) + GameState.createInitialGameState(notBlackJackAndNotBustHand) ); //when - GameResult result = testPlayer.calculateGameResult(dealerGameState); + GameResult result = testPlayer.calculateGameResult(testDealer); //then assertEquals(GameResult.class, result.getClass()); @@ -164,11 +164,11 @@ void equal_when_name_equal() { ); Player firstGump = Player.from( testName, - GameState.createPlayerInitialGameState(playerHand1) + GameState.createInitialGameState(playerHand1) ); Player secondGump = Player.from( testName, - GameState.createPlayerInitialGameState(playerHand2) + GameState.createInitialGameState(playerHand2) ); //when, then Assertions.assertThat(firstGump.equals(secondGump)).isTrue(); From e7c6a6b17f2de372c9e1a0565856293cebfca811 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Fri, 13 Mar 2026 18:46:04 +0900 Subject: [PATCH 124/129] feat : add in-out logic for betting --- src/main/java/view/InputViewImpl.java | 6 ++++++ src/main/java/view/OutputViewImpl.java | 6 +++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/src/main/java/view/InputViewImpl.java b/src/main/java/view/InputViewImpl.java index 8d7967a5f94..bf0549947a9 100644 --- a/src/main/java/view/InputViewImpl.java +++ b/src/main/java/view/InputViewImpl.java @@ -16,6 +16,12 @@ public List readNames() { return Arrays.stream(line.split(DELIMITER)).toList(); } + public String readBetAmountValue() { + String line = sc.nextLine(); + validateIsBlank(line); + return line; + } + public Boolean wantToHit() { String input = sc.nextLine().trim(); validateIsBlank(input); diff --git a/src/main/java/view/OutputViewImpl.java b/src/main/java/view/OutputViewImpl.java index dcf69081bbb..310c74f2edc 100644 --- a/src/main/java/view/OutputViewImpl.java +++ b/src/main/java/view/OutputViewImpl.java @@ -14,6 +14,7 @@ public class OutputViewImpl implements OutputView { private static final String DELIMITER = ", "; private static final String NAME_PROMPT = "게임에 참여할 사람의 이름을 입력하세요.(쉼표 기준으로 분리)"; + private static final String BET_AMOUNT_PROMPT = "\n%s의 배팅 금액은?\n"; private static final String INITIAL_CARD_SHARE = "\n딜러와 %s에게 2장을 나누었습니다.\n"; private static final String HIT_OR_STAND_PROMPT = "%s는 한장의 카드를 더 받겠습니까?(예는 y, 아니오는 n)\n"; private static final String DEALER_ADD_CARD_NOTICE = "\n딜러는 16이하라 한장의 카드를 더 받았습니다.\n"; @@ -30,7 +31,10 @@ public void printNamePrompt() { System.out.println(NAME_PROMPT); } - @Override + public void printBetAmountPrompt(ParticipantDto participantDto) { + System.out.printf(BET_AMOUNT_PROMPT, participantDto.name()); + } + public void printInitialStates(GameStateDto gameStateDto) { List playersDtos = gameStateDto.multiPlayersDtos(); ParticipantDto dealerDto = gameStateDto.dealerDto(); From 9c1a233681572275c42fb897e89dcb53af5cae24 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Fri, 13 Mar 2026 19:05:51 +0900 Subject: [PATCH 125/129] feat : add bet Process --- src/main/java/common/ErrorMessage.java | 4 +++- .../java/controller/BlackJackController.java | 15 +++++++++++-- src/main/java/domain/BetAmount.java | 21 +++++++++++++++++-- src/main/java/domain/BlackJackGame.java | 12 ++++++++++- src/main/java/domain/Dealer.java | 21 ++++++++----------- src/main/java/domain/MultiPlayers.java | 10 +++++++++ src/main/java/domain/Participant.java | 9 +------- src/main/java/domain/Player.java | 13 +++++++++--- src/main/java/dto/PlayerResultDto.java | 2 +- src/main/java/view/InputView.java | 2 ++ src/main/java/view/OutputView.java | 2 ++ src/main/java/view/OutputViewImpl.java | 4 ++-- .../controller/BlackJackControllerTest.java | 4 ++++ src/test/java/domain/BlackJackGameTest.java | 2 +- 14 files changed, 88 insertions(+), 33 deletions(-) diff --git a/src/main/java/common/ErrorMessage.java b/src/main/java/common/ErrorMessage.java index 8f6b0121c65..3c0061097d6 100644 --- a/src/main/java/common/ErrorMessage.java +++ b/src/main/java/common/ErrorMessage.java @@ -8,9 +8,11 @@ public enum ErrorMessage { ONLY_KO_AND_ENG("이름은 영어 또는 한국어만 가능합니다: "), NAME_UNIQUENESS_ERR("이름은 중복되면 안됩니다"), PLAYER_NOT_FOUND("해당 플레이어를 찾을 수 없습니다"), - NO_MORE_PLAYABLE_PLAYER("더 이상 게임을 진행할 수 있는 플레이어가 없습니다."), + NO_MORE_PLAYABLE_PLAYER("더 이상 게임을 진행할 수 있는 플레이어를 찾지 못했습니다"), + NO_MORE_BETTABLE_PLAYER("베팅을 진행할 수 있는 플레이어를 찾지 못했습니다"), NOT_ALLOW_METHOD_CALL("현재 상태에서 허용되지 않는 메소드 호출입니다"), ZERO_MINUS_MONEY("0원 이하 금액을 베팅할 수는 없습니다"), + ONLY_NUMBER("숫자만 입력 가능합니다"), MAX_PLAYER_ERROR("최대 인원을 초과했습니다."); private final String message; diff --git a/src/main/java/controller/BlackJackController.java b/src/main/java/controller/BlackJackController.java index 9799cde1454..5509a2bfeed 100644 --- a/src/main/java/controller/BlackJackController.java +++ b/src/main/java/controller/BlackJackController.java @@ -27,6 +27,8 @@ public void doGameProcess() { BlackJackGame game = retry(this::readyGame); outputView.printInitialStates(game.getGameSettingState()); + betPlayers(game); + playPlayersTurn(game); playDealerTurn(game); @@ -34,6 +36,15 @@ public void doGameProcess() { outputView.printGameResult(gameResults); } + private void betPlayers(BlackJackGame game) { + while (game.whoseBettingTurn().isPresent()) { + Player currentPlayer = game.whoseBettingTurn().get(); + outputView.printBetAmountPrompt(currentPlayer.getName()); + String betAmountValue = retry(inputView::readBetAmountValue); + game.doBetProcess(betAmountValue); + } + } + private BlackJackGame readyGame() { outputView.printNamePrompt(); List playerNames = inputView.readNames(); @@ -47,8 +58,8 @@ private void playDealerTurn(BlackJackGame game) { } private void playPlayersTurn(BlackJackGame game) { - while (game.whoseTurn().isPresent()) { - Player currentPlayer = game.whoseTurn().get(); + while (game.whosePlayTurn().isPresent()) { + Player currentPlayer = game.whosePlayTurn().get(); outputView.printHitOrStandPrompt(currentPlayer.getName()); boolean wantToHit = retry(inputView::wantToHit); doHitOrStand(wantToHit, game); diff --git a/src/main/java/domain/BetAmount.java b/src/main/java/domain/BetAmount.java index fa22b0b01e4..6869a6ee187 100644 --- a/src/main/java/domain/BetAmount.java +++ b/src/main/java/domain/BetAmount.java @@ -5,15 +5,32 @@ public record BetAmount( int betAmount ) { - public static BetAmount of(int betAmount) { + public static BetAmount of(String betAmountValue) { + int betAmount = parseBetAmount(betAmountValue); + validatePositiveNumber(betAmount); + return new BetAmount(betAmount); + } + + private static void validatePositiveNumber(int betAmount) { if (betAmount <= 0) { throw new IllegalArgumentException(ErrorMessage.ZERO_MINUS_MONEY.getMessage()); } - return new BetAmount(betAmount); + } + + private static int parseBetAmount(String betAmountValue) { + try { + return Integer.parseInt(betAmountValue); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(ErrorMessage.ONLY_NUMBER.getMessage()); + } } public static BetAmount empty() { return new BetAmount(0); } + + public boolean isBetPlaced() { + return betAmount != 0; + } } diff --git a/src/main/java/domain/BlackJackGame.java b/src/main/java/domain/BlackJackGame.java index b10c0d8c7fe..727a071f76c 100644 --- a/src/main/java/domain/BlackJackGame.java +++ b/src/main/java/domain/BlackJackGame.java @@ -41,10 +41,20 @@ private static Dealer createNewDealer(Deck totalDeck) { ); } - public Optional whoseTurn() { + public Optional whoseBettingTurn() { + return multiPlayers.findNotBetPlayer(); + } + + public Optional whosePlayTurn() { return multiPlayers.findNotStayPlayer(); } + public void doBetProcess(String betAmountValue) { + Player target = multiPlayers.findNotBetPlayer() + .orElseThrow(() -> new IllegalStateException(ErrorMessage.NO_MORE_BETTABLE_PLAYER.getMessage())); + multiPlayers.executeBet(target, betAmountValue); + } + public ParticipantDto doHitProcess() { Player newPlayer = multiPlayers.findNotStayPlayer() .map(player -> multiPlayers.executeHit(player, totalDeck::drawCard)) diff --git a/src/main/java/domain/Dealer.java b/src/main/java/domain/Dealer.java index 089fbd15873..84618cdcc04 100644 --- a/src/main/java/domain/Dealer.java +++ b/src/main/java/domain/Dealer.java @@ -7,19 +7,12 @@ public class Dealer extends Participant { private static final String DEALER_NAME = "딜러"; - private Dealer(GameState gameState, BetAmount betAmount) { - super(DEALER_NAME, gameState, betAmount); + private Dealer(GameState gameState) { + super(DEALER_NAME, gameState); } public static Dealer from(GameState gameState) { - return new Dealer(gameState, BetAmount.empty()); - } - - public Dealer bet(int amount) { - return new Dealer( - this.gameState, - BetAmount.of(amount) - ); + return new Dealer(gameState); } @Override @@ -29,7 +22,11 @@ public List showInitialCard() { } public Dealer addCard(Supplier cardSupplier) { - GameState newGameState = gameState.hit(cardSupplier); - return Dealer.from(newGameState); + if (gameState.getCardsSum() <= 16) { + GameState newGameState = gameState.hit(cardSupplier); + return Dealer.from(newGameState); + } + GameState stayGameState = gameState.stay(); + return Dealer.from(stayGameState); } } diff --git a/src/main/java/domain/MultiPlayers.java b/src/main/java/domain/MultiPlayers.java index f39508e3aac..fafb5f954a4 100644 --- a/src/main/java/domain/MultiPlayers.java +++ b/src/main/java/domain/MultiPlayers.java @@ -54,6 +54,16 @@ private static void validateUserCountLimit(List playerNames) { } } + public Optional findNotBetPlayer() { + return players.values().stream() + .filter(player -> !player.isBet()) + .findFirst(); + } + + public void executeBet(Player player, String betMoneyValue) { + applyAction(player.getName(), p -> p.bet(betMoneyValue)); + } + public Optional findNotStayPlayer() { return players.values().stream() .filter(Player::isPlayable) diff --git a/src/main/java/domain/Participant.java b/src/main/java/domain/Participant.java index dac7943ab45..1286f24bc96 100644 --- a/src/main/java/domain/Participant.java +++ b/src/main/java/domain/Participant.java @@ -2,17 +2,14 @@ import domain.state.GameState; import java.util.List; -import java.util.Objects; public abstract class Participant { protected final GameState gameState; protected final ParticipantName participantName; - protected final BetAmount betAmount; - protected Participant(String name, GameState gameState, BetAmount betAmount) { + protected Participant(String name, GameState gameState) { this.participantName = new ParticipantName(name); this.gameState = gameState; - this.betAmount = betAmount; } public abstract List showInitialCard(); @@ -21,10 +18,6 @@ public String getName() { return participantName.name(); } - public boolean isBet() { - return !Objects.equals(betAmount, BetAmount.empty()); - } - public List showOwnCards() { return gameState.showOwnCards(); } diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java index 680caef9a25..e54e37df117 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/Player.java @@ -6,22 +6,29 @@ import java.util.function.Supplier; public class Player extends Participant { + private final BetAmount betAmount; + private Player(String name, GameState gameState, BetAmount betAmount) { - super(name, gameState, betAmount); + super(name, gameState); + this.betAmount = betAmount; } public static Player from(String name, GameState gameState) { return new Player(name, gameState, BetAmount.empty()); } - public Player bet(int amount) { + public Player bet(String betAmountValue) { return new Player( this.participantName.name(), this.gameState, - BetAmount.of(amount) + BetAmount.of(betAmountValue) ); } + public boolean isBet() { + return betAmount.isBetPlaced(); + } + public Player hit(Supplier cardSupplier) { GameState newGameState = gameState.hit(cardSupplier); return new Player( diff --git a/src/main/java/dto/PlayerResultDto.java b/src/main/java/dto/PlayerResultDto.java index ada00f9555b..232279d7d9f 100644 --- a/src/main/java/dto/PlayerResultDto.java +++ b/src/main/java/dto/PlayerResultDto.java @@ -16,4 +16,4 @@ public static PlayerResultDto from(Player player, Dealer dealer) { player.calculateGameResult(dealer) ); } -} +} \ No newline at end of file diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 53bb1a4ed62..8cfa4c95999 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -5,5 +5,7 @@ public interface InputView { List readNames(); + String readBetAmountValue(); + Boolean wantToHit(); } diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 1b0675c75dd..44b27b4b427 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -9,6 +9,8 @@ public interface OutputView { void printNamePrompt(); + void printBetAmountPrompt(String name); + void printInitialStates(GameStateDto gameStateDto); void printHitOrStandPrompt(String name); diff --git a/src/main/java/view/OutputViewImpl.java b/src/main/java/view/OutputViewImpl.java index 310c74f2edc..3f9533ff713 100644 --- a/src/main/java/view/OutputViewImpl.java +++ b/src/main/java/view/OutputViewImpl.java @@ -31,8 +31,8 @@ public void printNamePrompt() { System.out.println(NAME_PROMPT); } - public void printBetAmountPrompt(ParticipantDto participantDto) { - System.out.printf(BET_AMOUNT_PROMPT, participantDto.name()); + public void printBetAmountPrompt(String name) { + System.out.printf(BET_AMOUNT_PROMPT, name); } public void printInitialStates(GameStateDto gameStateDto) { diff --git a/src/test/java/controller/BlackJackControllerTest.java b/src/test/java/controller/BlackJackControllerTest.java index 74afc21c7ff..85417b080d3 100644 --- a/src/test/java/controller/BlackJackControllerTest.java +++ b/src/test/java/controller/BlackJackControllerTest.java @@ -65,6 +65,10 @@ public void printErrorMessage(Exception e) { public void printNamePrompt() { } + @Override + public void printBetAmountPrompt(String name) { + } + @Override public void printInitialStates(GameStateDto gameStateDto) { } diff --git a/src/test/java/domain/BlackJackGameTest.java b/src/test/java/domain/BlackJackGameTest.java index bcbf1a1e8c9..79dd0b436e6 100644 --- a/src/test/java/domain/BlackJackGameTest.java +++ b/src/test/java/domain/BlackJackGameTest.java @@ -38,7 +38,7 @@ void ready_good() { void whoseTure_success() { BlackJackGame testGame = BlackJackGame.ready(TEST_PLAYER_NAMES, this::createSampleCards); - Optional result = testGame.whoseTurn(); + Optional result = testGame.whosePlayTurn(); assertTrue(result.isPresent()); } From 1f42451ba9ad63a5f3fb8cfee4f782877426f385 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Fri, 13 Mar 2026 19:42:10 +0900 Subject: [PATCH 126/129] fix : change result printing logic --- src/main/java/domain/GameResult.java | 21 ++++++++-- src/main/java/domain/Player.java | 4 ++ src/main/java/dto/DealerResultDto.java | 24 +++--------- src/main/java/dto/PlayerResultDto.java | 6 ++- src/main/java/view/OutputViewImpl.java | 38 +++++++++---------- .../controller/BlackJackControllerTest.java | 5 +++ src/test/java/domain/GameResultTest.java | 4 +- 7 files changed, 57 insertions(+), 45 deletions(-) diff --git a/src/main/java/domain/GameResult.java b/src/main/java/domain/GameResult.java index b619fbe9cec..741695722c3 100644 --- a/src/main/java/domain/GameResult.java +++ b/src/main/java/domain/GameResult.java @@ -3,12 +3,25 @@ import domain.state.GameState; public enum GameResult { - 승, 무, 패; + 블랙잭(1.5), + 승(1), + 무(0), + 패(-1); + + private final double allocation; + + GameResult(double allocation) { + this.allocation = allocation; + } + + public double getAllocation() { + return allocation; + } public static GameResult decidePlayerResult(Player player, Dealer dealer) { GameState playerGameState = player.gameState; GameState dealerGameState = dealer.gameState; - + if (playerGameState.isBust()) { return GameResult.패; } @@ -19,7 +32,7 @@ public static GameResult decidePlayerResult(Player player, Dealer dealer) { return GameResult.무; } if (playerGameState.isBlackJack()) { - return GameResult.승; + return GameResult.블랙잭; } if (dealerGameState.isBlackJack()) { return GameResult.패; @@ -48,4 +61,4 @@ public GameResult reverse() { return GameResult.무; } -} +} \ No newline at end of file diff --git a/src/main/java/domain/Player.java b/src/main/java/domain/Player.java index e54e37df117..800e0eec572 100644 --- a/src/main/java/domain/Player.java +++ b/src/main/java/domain/Player.java @@ -29,6 +29,10 @@ public boolean isBet() { return betAmount.isBetPlaced(); } + public int getBetAmount() { + return betAmount.betAmount(); + } + public Player hit(Supplier cardSupplier) { GameState newGameState = gameState.hit(cardSupplier); return new Player( diff --git a/src/main/java/dto/DealerResultDto.java b/src/main/java/dto/DealerResultDto.java index 0a1748b4ded..85f66418e10 100644 --- a/src/main/java/dto/DealerResultDto.java +++ b/src/main/java/dto/DealerResultDto.java @@ -1,32 +1,20 @@ package dto; import domain.Dealer; -import domain.GameResult; -import java.util.EnumMap; import java.util.List; -import java.util.Map; public record DealerResultDto( ParticipantDto dealerDto, int score, - Map dealerResult + double dealerEarnMoney ) { public static DealerResultDto from(Dealer dealer, List playerResults) { - Map totalResult = initializeTotalResult(); + double earnMoney = 0; - playerResults.stream() - .map(PlayerResultDto::result) - .map(GameResult::reverse) - .forEach(result -> totalResult.merge(result, 1, Integer::sum)); - - return new DealerResultDto(ParticipantDto.from(dealer), dealer.getOwnCardsSum(), totalResult); - } - - private static Map initializeTotalResult() { - Map map = new EnumMap<>(GameResult.class); - for (GameResult result : GameResult.values()) { - map.put(result, 0); + for (PlayerResultDto playerResult : playerResults) { + earnMoney += playerResult.playerEarnMoney() * -1; } - return map; + + return new DealerResultDto(ParticipantDto.from(dealer), dealer.getOwnCardsSum(), earnMoney); } } \ No newline at end of file diff --git a/src/main/java/dto/PlayerResultDto.java b/src/main/java/dto/PlayerResultDto.java index 232279d7d9f..632c13dc0e2 100644 --- a/src/main/java/dto/PlayerResultDto.java +++ b/src/main/java/dto/PlayerResultDto.java @@ -7,13 +7,15 @@ public record PlayerResultDto( ParticipantDto playerDto, int score, - GameResult result + double playerEarnMoney ) { public static PlayerResultDto from(Player player, Dealer dealer) { + GameResult gameResult = player.calculateGameResult(dealer); + double earnMoney = gameResult.getAllocation() * player.getBetAmount(); return new PlayerResultDto( ParticipantDto.from(player), player.getOwnCardsSum(), - player.calculateGameResult(dealer) + earnMoney ); } } \ No newline at end of file diff --git a/src/main/java/view/OutputViewImpl.java b/src/main/java/view/OutputViewImpl.java index 3f9533ff713..fcd749c6dc0 100644 --- a/src/main/java/view/OutputViewImpl.java +++ b/src/main/java/view/OutputViewImpl.java @@ -1,14 +1,14 @@ package view; -import domain.GameResult; import dto.CardDto; import dto.DealerResultDto; import dto.GameResultDto; import dto.GameStateDto; import dto.ParticipantDto; import dto.PlayerResultDto; +import java.text.NumberFormat; import java.util.List; -import java.util.Map; +import java.util.Locale; import java.util.stream.Collectors; public class OutputViewImpl implements OutputView { @@ -21,7 +21,7 @@ public class OutputViewImpl implements OutputView { private static final String PARTICIPANT_CARD_INFO_FORMAT = "%s카드: %s"; private static final String WIN_LOSS_RESULT_HEADER = "\n## 최종 승패"; private static final String PARTICIPANT_CARD_INFO_WITH_SUM_FORMAT = "%s - 결과: %d\n"; - private static final String WIN_LOSS_RESULT_FORMAT = "%s: %s\n"; + private static final String RESULT_FORMAT = "%s: %s\n"; public void printErrorMessage(Exception e) { System.out.println(e.getMessage()); @@ -85,9 +85,7 @@ private void printFinalWinLoss(GameResultDto gameResultDto) { System.out.print(formatDealerResult(dealerResultDto)); List playerResultDtos = gameResultDto.playerResultDto(); - playerResultDtos.forEach(dto -> - System.out.printf(WIN_LOSS_RESULT_FORMAT, dto.playerDto().name(), dto.result().name()) - ); + playerResultDtos.forEach(dto -> System.out.printf(formatPlayerResult(dto))); } private String formatParticipantCards(ParticipantDto participantDto) { @@ -103,18 +101,20 @@ private String formatCardsInfo(List cards) { private String formatDealerResult(DealerResultDto dealerResultDto) { String dealerName = dealerResultDto.dealerDto().name(); - String resultString = formatDealerResultInfo(dealerResultDto.dealerResult()); - return String.format(WIN_LOSS_RESULT_FORMAT, dealerName, resultString); - } - - private String formatDealerResultInfo(Map dealerGameResult) { - StringBuilder sb = new StringBuilder(); - for (GameResult result : GameResult.values()) { - int count = dealerGameResult.getOrDefault(result, 0); - if (count > 0) { - sb.append(count).append(result.name()).append(" "); - } - } - return sb.toString().trim(); + double dealerEarnMoney = dealerResultDto.dealerEarnMoney(); + String formattedDealerEarnMoney = formatToKoreanCurrencyInstance(dealerEarnMoney); + return String.format(RESULT_FORMAT, dealerName, formattedDealerEarnMoney); + } + + private String formatPlayerResult(PlayerResultDto playerResultDto) { + String dealerName = playerResultDto.playerDto().name(); + double dealerEarnMoney = playerResultDto.playerEarnMoney(); + String formattedPlayerEarnMoney = formatToKoreanCurrencyInstance(dealerEarnMoney); + return String.format(RESULT_FORMAT, dealerName, formattedPlayerEarnMoney); + } + + private String formatToKoreanCurrencyInstance(double money) { + NumberFormat krwFormat = NumberFormat.getCurrencyInstance(Locale.KOREA); + return krwFormat.format(money); } } diff --git a/src/test/java/controller/BlackJackControllerTest.java b/src/test/java/controller/BlackJackControllerTest.java index 85417b080d3..161c3d620f1 100644 --- a/src/test/java/controller/BlackJackControllerTest.java +++ b/src/test/java/controller/BlackJackControllerTest.java @@ -49,6 +49,11 @@ public List readNames() { return List.of("pobi", "jason", "gump"); } + @Override + public String readBetAmountValue() { + return "100000"; + } + @Override public Boolean wantToHit() { return hitOrStandOrder.poll(); diff --git a/src/test/java/domain/GameResultTest.java b/src/test/java/domain/GameResultTest.java index be8d66a20a7..e52b31ea0cd 100644 --- a/src/test/java/domain/GameResultTest.java +++ b/src/test/java/domain/GameResultTest.java @@ -34,7 +34,7 @@ class decidePlayerResultTest { class blackJackCaseTest { @Test - @DisplayName("플레이어가 블랙잭이면 플레이어가 이긴 결과를 도출한다") + @DisplayName("플레이어가 블랙잭이면 플레이어가 블랙잭으로 이긴 결과를 도출한다") void decidePlayerResult_player_blackjack() { //given Dealer testDealer = Dealer.from(GameState.createInitialGameState(normalHand)); @@ -44,7 +44,7 @@ void decidePlayerResult_player_blackjack() { GameResult result = GameResult.decidePlayerResult(testPlayer, testDealer); //then - assertEquals(GameResult.승, result); + assertEquals(GameResult.블랙잭, result); } @Test From d8c08243491636e2a280c58a623b19481be4c106 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Fri, 13 Mar 2026 20:27:50 +0900 Subject: [PATCH 127/129] test : add test of betting logic --- src/main/java/domain/Dealer.java | 3 +- src/test/java/domain/BetAmountTest.java | 61 +++++++++++++++++++++ src/test/java/domain/BlackJackGameTest.java | 32 +++++++++-- src/test/java/domain/DeckTest.java | 12 ++-- src/test/java/domain/MultiPlayersTest.java | 53 +++++++++++++++++- src/test/java/domain/PlayerTest.java | 46 +++++++++++----- 6 files changed, 179 insertions(+), 28 deletions(-) create mode 100644 src/test/java/domain/BetAmountTest.java diff --git a/src/main/java/domain/Dealer.java b/src/main/java/domain/Dealer.java index 84618cdcc04..7eaa4230c98 100644 --- a/src/main/java/domain/Dealer.java +++ b/src/main/java/domain/Dealer.java @@ -6,6 +6,7 @@ public class Dealer extends Participant { private static final String DEALER_NAME = "딜러"; + private static final int DEALER_CARD_SUM_MIN = 16; private Dealer(GameState gameState) { super(DEALER_NAME, gameState); @@ -22,7 +23,7 @@ public List showInitialCard() { } public Dealer addCard(Supplier cardSupplier) { - if (gameState.getCardsSum() <= 16) { + if (gameState.getCardsSum() <= DEALER_CARD_SUM_MIN) { GameState newGameState = gameState.hit(cardSupplier); return Dealer.from(newGameState); } diff --git a/src/test/java/domain/BetAmountTest.java b/src/test/java/domain/BetAmountTest.java new file mode 100644 index 00000000000..6e93aa4400c --- /dev/null +++ b/src/test/java/domain/BetAmountTest.java @@ -0,0 +1,61 @@ +package domain; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import common.ErrorMessage; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class BetAmountTest { + + @Test + @DisplayName("숫자가 아닌 값으로 생성 시도 시 오류 발생") + void of_fail_not_number() { + //given + String notNumber = "NaN"; + + //when + assertThatThrownBy( + () -> BetAmount.of(notNumber) + ).isInstanceOf(IllegalArgumentException.class) + .hasMessage(ErrorMessage.ONLY_NUMBER.getMessage()); + } + + @Test + @DisplayName("양수가 아니면 오류 발생") + void of_fail_not_positive_num() { + //given + String notPosNum = "-1"; + //when + assertThatThrownBy( + () -> BetAmount.of(notPosNum) + ).isInstanceOf(IllegalArgumentException.class) + .hasMessage(ErrorMessage.ZERO_MINUS_MONEY.getMessage()); + + } + + @Test + @DisplayName("양수면 정상 생성") + void of_good() { + String posNum = "10000"; + + assertDoesNotThrow( + () -> BetAmount.of(posNum) + ); + } + + @Test + @DisplayName("0이면 베팅을 안한 것으로 친다") + void zero_mean_not_bet_yet() { + //given + String zero = "0"; + + //when + BetAmount testBetAmount = BetAmount.of(zero); + + //then + assertTrue(testBetAmount.isBetPlaced()); + } +} \ No newline at end of file diff --git a/src/test/java/domain/BlackJackGameTest.java b/src/test/java/domain/BlackJackGameTest.java index 79dd0b436e6..c18813da5aa 100644 --- a/src/test/java/domain/BlackJackGameTest.java +++ b/src/test/java/domain/BlackJackGameTest.java @@ -24,18 +24,25 @@ class BlackJackGameTest { @Test @DisplayName("생성 잘 한다") void ready_good() { - //given - Deck sampleDeck = Deck.createDeck(this::createSampleCards); - //when, then assertDoesNotThrow( () -> BlackJackGame.ready(TEST_PLAYER_NAMES, this::createSampleCards) ); } + @Test + @DisplayName("베팅을 안한 사용자가 있으면 제공한다") + void whoseBettingTurn_exist() { + BlackJackGame testGame = BlackJackGame.ready(TEST_PLAYER_NAMES, this::createSampleCards); + + Optional result = testGame.whoseBettingTurn(); + + assertTrue(result.isPresent()); + } + @Test @DisplayName("다음 턴의 사용자가 있으면 제공한다") - void whoseTure_success() { + void whosePlayTurn_exist() { BlackJackGame testGame = BlackJackGame.ready(TEST_PLAYER_NAMES, this::createSampleCards); Optional result = testGame.whosePlayTurn(); @@ -43,6 +50,23 @@ void whoseTure_success() { assertTrue(result.isPresent()); } + // // + // // public void doBetProcess(String betAmountValue) { + // // Player target = multiPlayers.findNotBetPlayer() + // // .orElseThrow(() -> new IllegalStateException(ErrorMessage.NO_MORE_BETTABLE_PLAYER.getMessage())); + // // multiPlayers.executeBet(target, betAmountValue); + // // } + @Test + @DisplayName("bet을 시켜도 오류가 안난다") + void doBetProcess_success() { + String betAmountValue = "1000"; + BlackJackGame testGame = BlackJackGame.ready(TEST_PLAYER_NAMES, this::createSampleCards); + + assertDoesNotThrow( + () -> testGame.doBetProcess(betAmountValue) + ); + } + @Test @DisplayName("hit 프로세스를 적합한 턴의 플레이어에게 요청하고 결과로 ParticipantDto를 반환받는다") void doHitProcess_success() { diff --git a/src/test/java/domain/DeckTest.java b/src/test/java/domain/DeckTest.java index a81a0ceaf1d..d74e40aed51 100644 --- a/src/test/java/domain/DeckTest.java +++ b/src/test/java/domain/DeckTest.java @@ -7,7 +7,6 @@ import common.ErrorMessage; import java.util.ArrayDeque; -import java.util.Deque; import java.util.List; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; @@ -16,14 +15,11 @@ class DeckTest { Deck deck; - CardCreationStrategy fixedCardCreationStrategy = new CardCreationStrategy() { - @Override - public Deque create() { - Card spadeJ = new Card(CardShape.스페이드, CardContents.J); - Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); + CardCreationStrategy fixedCardCreationStrategy = () -> { + Card spadeJ = new Card(CardShape.스페이드, CardContents.J); + Card clover5 = new Card(CardShape.클로버, CardContents.FIVE); - return new ArrayDeque<>(List.of(spadeJ, clover5)); - } + return new ArrayDeque<>(List.of(spadeJ, clover5)); }; @BeforeEach diff --git a/src/test/java/domain/MultiPlayersTest.java b/src/test/java/domain/MultiPlayersTest.java index a0ff23b648a..b10346bc6f7 100644 --- a/src/test/java/domain/MultiPlayersTest.java +++ b/src/test/java/domain/MultiPlayersTest.java @@ -70,6 +70,43 @@ void of_fail_too_many_players() { } } + @Nested + class findNotBetPlayer { + @Test + @DisplayName("isBet가 False인 사용자가 있으면 Player를 반환한다") + void findNotStayPlayer_exist() { + //given + MultiPlayers testMultiPlayers = MultiPlayers.of(TEST_PLAYER_NAMES, totalDeck); + + //when + Optional result = testMultiPlayers.findNotStayPlayer(); + + //then + assertTrue(result.isPresent()); + } + + @Test + @DisplayName("모든 플레이어가 베팅을 끝내면 없으면 빈 Optional를 반환한다") + void findNotStayPlayer_not_exist() { + //given + String commonBetAmountValue = "1000"; + MultiPlayers testMultiPlayers = MultiPlayers.of(TEST_PLAYER_NAMES, totalDeck); + while (true) { + Optional findResult = testMultiPlayers.findNotBetPlayer(); + if (findResult.isEmpty()) { + break; + } + testMultiPlayers.executeBet(findResult.get(), commonBetAmountValue); + } + + //when + Optional result = testMultiPlayers.findNotBetPlayer(); + + //then + assertTrue(result.isEmpty()); + } + } + @Nested class findNotStayPlayerTest { @Test @@ -86,7 +123,7 @@ void findNotStayPlayer_exist() { } @Test - @DisplayName("stay가 아닌 사용자가 없으면 빈 Optional를 반환한다") + @DisplayName("모든 Player가 종료된 상태가 되면 빈 Optional를 반환한다") void findNotStayPlayer_not_exist() { //given MultiPlayers testMultiPlayers = MultiPlayers.of(TEST_PLAYER_NAMES, totalDeck); @@ -135,6 +172,20 @@ void executeStand_good() { //then assertEquals(Player.class, result.getClass()); } + + @Test + @DisplayName("양수의 금액으로 bet를 플레이이에게 요청 시 오류가 발생하지 않는다.") + void executeBet_good() { + //given + String betMoney = "10000"; + MultiPlayers testMultiPlayers = MultiPlayers.of(TEST_PLAYER_NAMES, totalDeck); + Player testPlayer = testMultiPlayers.findNotStayPlayer().get(); + + //when, then + assertDoesNotThrow( + () -> testMultiPlayers.executeBet(testPlayer, betMoney) + ); + } } @Test diff --git a/src/test/java/domain/PlayerTest.java b/src/test/java/domain/PlayerTest.java index fff916fabeb..5c34cfccc97 100644 --- a/src/test/java/domain/PlayerTest.java +++ b/src/test/java/domain/PlayerTest.java @@ -3,6 +3,7 @@ import static org.assertj.core.api.AssertionsForClassTypes.assertThatThrownBy; import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertTrue; import common.ErrorMessage; import domain.state.GameState; @@ -16,6 +17,8 @@ import org.junit.jupiter.api.Test; class PlayerTest { + private static final String GUMP = "gump"; + @Test @DisplayName("Player를 생성할 때 오류 발생 안함") void player_create_success() { @@ -24,10 +27,8 @@ void player_create_success() { new Card(CardShape.클로버, CardContents.K) ); - String name = "pobi"; - assertDoesNotThrow( - () -> Player.from(name, GameState.createInitialGameState(playerHand)) + () -> Player.from(GUMP, GameState.createInitialGameState(playerHand)) ); } @@ -45,9 +46,8 @@ class hitTest { )); Supplier onlyTwoTenCardSupplier = onlyTwoTenCards::poll; - String testName = "gump"; Player testPlayerWhoHoldTotal15Cards = Player.from( - testName, + GUMP, GameState.createInitialGameState(playerHand) ); @@ -88,9 +88,8 @@ void hit_butBust_and_finish() { new Card(CardShape.스페이드, CardContents.TEN), new Card(CardShape.클로버, CardContents.TEN) ); - String testName = "gump"; Player testPlayer = Player.from( - testName, + GUMP, GameState.createInitialGameState(playerHand) ); testPlayer.hit(onlyTwoTenCardSupplier); @@ -111,9 +110,8 @@ void stand_and_finish() { new Card(CardShape.스페이드, CardContents.J), new Card(CardShape.클로버, CardContents.FIVE) ); - String testName = "gump"; Player testPlayer = Player.from( - testName, + GUMP, GameState.createInitialGameState(playerHand) ); @@ -128,7 +126,6 @@ void stand_and_finish() { @DisplayName("compare는 게임 결과 객체를 잘 반환한다") void lose_when_dealer_blackjack() { //given - String testPlayerName = "rati"; Hand blackJackHand = Hand.of( new Card(CardShape.스페이드, CardContents.J), new Card(CardShape.클로버, CardContents.A) @@ -139,7 +136,7 @@ void lose_when_dealer_blackjack() { ); Dealer testDealer = Dealer.from(GameState.createInitialGameState(blackJackHand)); Player testPlayer = Player.from( - testPlayerName, + GUMP, GameState.createInitialGameState(notBlackJackAndNotBustHand) ); @@ -153,7 +150,6 @@ void lose_when_dealer_blackjack() { @Test @DisplayName("이름이 같으면 같은 Player로 본다") void equal_when_name_equal() { - String testName = "gump"; Hand playerHand1 = Hand.of( new Card(CardShape.스페이드, CardContents.A), new Card(CardShape.클로버, CardContents.TWO) @@ -163,14 +159,36 @@ void equal_when_name_equal() { new Card(CardShape.클로버, CardContents.FOUR) ); Player firstGump = Player.from( - testName, + GUMP, GameState.createInitialGameState(playerHand1) ); Player secondGump = Player.from( - testName, + GUMP, GameState.createInitialGameState(playerHand2) ); //when, then Assertions.assertThat(firstGump.equals(secondGump)).isTrue(); } + + @Test + @DisplayName("베팅을 하면 베팅 한 것으로 인정된다") + void bet_after_isBet_True() { + //given + Hand playerHand = Hand.of( + new Card(CardShape.스페이드, CardContents.A), + new Card(CardShape.클로버, CardContents.TWO) + ); + Player gump = Player.from( + GUMP, + GameState.createInitialGameState(playerHand) + ); + + String gumpBetAmountValue = "1000"; + + //when + Player bettedGump = gump.bet(gumpBetAmountValue); + + //then + assertTrue(bettedGump.isBet()); + } } \ No newline at end of file From 5df0e69f235f1164ef86ac68a96b1680d7b3cf8f Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Fri, 13 Mar 2026 20:35:47 +0900 Subject: [PATCH 128/129] fix : change io flow to meet requirements --- src/main/java/controller/BlackJackController.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/controller/BlackJackController.java b/src/main/java/controller/BlackJackController.java index 5509a2bfeed..ae7340a8854 100644 --- a/src/main/java/controller/BlackJackController.java +++ b/src/main/java/controller/BlackJackController.java @@ -25,9 +25,8 @@ public BlackJackController(InputView inputView, public void doGameProcess() { BlackJackGame game = retry(this::readyGame); - outputView.printInitialStates(game.getGameSettingState()); - betPlayers(game); + outputView.printInitialStates(game.getGameSettingState()); playPlayersTurn(game); playDealerTurn(game); From 4fd489c23dd17a6902774b5c766402ab30c0e716 Mon Sep 17 00:00:00 2001 From: kdongsu5509 Date: Fri, 13 Mar 2026 20:45:07 +0900 Subject: [PATCH 129/129] docs : update readme MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 퍼블릭 인터페이스를 기준으로 다시 작성하여 객체별 책임을 한 눈에 볼 수 있도록 정리하였음 --- README.md | 94 ++++++++++----------- src/test/java/domain/BlackJackGameTest.java | 6 -- 2 files changed, 43 insertions(+), 57 deletions(-) diff --git a/README.md b/README.md index 70b16d15c36..91b33ae5e8d 100644 --- a/README.md +++ b/README.md @@ -13,12 +13,16 @@ - **(예외 처리)** 공백 문자열인 경우 - **(예외 처리)** `y` 혹은 `n`이 아닌 경우 +- 베팅 금액을 입력받는다 + - 공백 문자열인 경우에 오류가 발생한다 + ### 출력 - 게임 안내 문구를 출력한다. - 이름 입력 안내 - HIT 혹은 STAND 여부 질문 - 딜러의 카드 추가 여부 + - 베팅 금액 입력 안내 - 초기 카드 내역을 출력한다. - 최종 카드 내역을 출력한다. - 최종 승패 결과를 출력한다. @@ -26,74 +30,62 @@ ## 도메인 ### MultiPlayers - - 플레이어들 생성 및 관리 - 이름 중복 검증 -- 플레이 가능한 사용자 찾기 -- 특정 플레이어에게 hit, stand 지시 -- 플레이어들의 게임 결과 조사 -- 플레이어들의 초기 카드 정보 조사 + - **(예외 처리)** 플레이어 수가 5명을 초과할 경우 예외 발생 (`IllegalArgumentException`) + - **(예외 처리)** 중복된 이름이 존재할 경우 예외 발생 (`IllegalArgumentException`) +- 플레이어 베팅 관리 + - **(예외 처리)** 등록되지 않은 플레이어에게 액션을 지시할 경우 예외 발생 +- 게임 진행 대기 중인(Hit/Stand 가능한) 플레이어 탐색 +- 특정 플레이어에게 Hit/Stand 지시 +- 모든 플레이어의 초기 (카드) 상태 및 게임 승패 결과 조회 ### Participant - - 이름 검증 - - 공백 불가 - - 한국어, 알파벳만 허용 -- Hand를 이용한 보유 카드 정보 제공 - - 초기 카드 - - 구성 - - 합 -- Hand를 이용한 Bust 정보 제공 -- Hand를 이용한 BlackJack 여부 제공 + - **(예외 처리)** 이름이 공백인 경우 예외 발생 + - **(예외 처리)** 영어나 한글 외의 문자가 포함된 경우 예외 발생 +- 참가자의 초기 부여 카드 및 전체 보유 카드 반환 +- 참가자가 베팅을 마쳤는지 여부 반환 +- 참가자가 보유한 카드 숫자의 총합 반환 ### Dealer - -- 조건에 따른 카드 추가 - - 점수 합이 16 이하인 경우 +- 점수 합이 16 이하인 경우 조건에 따른 카드 추가(Hit) +- 점수가 16을 초과할 경우 자동으로 카드 추가 중지(Stay) 상태로 전환 +- 딜러의 초기 카드(1장) 반환 기능 ### Player - -- 게임 진행 가능 여부 판별 - - Hand를 이용하여 isBust 판별 -- 게임 진행 의사 관리 -- 초기 보유 카드 보여주기 -- Hand를 이용한 카드 추가 -- 조건에 따른 카드 추가 - - 버스트 상태가 아닐 경우 +- 배팅 금액 관리 및 베팅 상태 점검 +- 플레이어 단위의 Hit/Stand 게임 진행 액션 수행 +- 게임 진행 가능 여부(Bust 또는 Blackjack 상태가 아님) 판별 +- 플레이어 턴의 종료 여부 확인 및 딜러와의 승패 결과(GameResult) 계산 +- 초기 부여받은 2장의 보유 카드 반환 ### Card +- 카드의 형상(Spade, Heart, Diamond, Club) 및 숫자(Ace ~ King) 정보 관리 및 제공 -- 카드 정보 관리 및 제공 +### BetAmount +- 배팅 금액 추적 및 예외 상황 관리 + - **(예외 처리)** 정수로 변환 불가능한 값 입력 시 예외 발생 (`IllegalArgumentException`) + - **(예외 처리)** 베팅 금액이 0원 이하일 경우 예외 발생 (`IllegalArgumentException`) ### Deck - -- 전체 카드 관리 -- 카드 1장 뽑기 -- 카드 2장 뽑기 +- 전략(Random 등) 기반 덱 구성 및 전체 카드 관리 +- 초기 부여용 카드 2장 뽑기 및 카드 1장 뽑기 + - **(예외 처리)** 덱에 더 이상 카드가 남아있지 않은 상태에서 카드를 뽑으려 할 경우 예외 발생 (`IllegalArgumentException`) ### Hand - -- 사용자 보유 카드 관리 -- Bust 판정 -- blackJack 판정 -- 게임 조건에 따른 카드 추가 -- 총점 계산 +- 보유 카드의 상태(Bust, Blackjack, 보유 카드 총점) 판단 +- 게임 조건에 맞춰 카드를 추가해 새로운 핸드 구성 반환 ### BlackJackGame - -- 게임 준비 - - 딜러, 플레이어들, 전체 덱 생성 -- 게임의 turn 관리. -- 특정 사용자를 찾아 hit / stand 실행 -- 모든 게임 참가자들의 초기 정보 조사 -- 모든 게임 참가자들의 결과 조사 +- 게임 준비 (딜러, 멀티 플레이어들, 전체 덱 생성) +- 현재 누구의 턴(베팅 턴 또는 플레이 턴)인지 확인 및 상태 추적 +- 현재 턴인 사용자에 맞게 Hit, Stand, 혹은 Dealer Play 단계 실행 + - **(예외 처리)** 베팅을 진행할 플레이어가 없는데 베팅을 시도할 경우 예외 발생 (`IllegalStateException`) + - **(예외 처리)** 카드를 더 이상 받을 수 있는 플레이어가 없는데 Hit/Stand를 시도할 경우 예외 발생 (`IllegalStateException`) +- 모든 게임 참가자들의 초기 상태 및 최종 결과 정보 조사 반환 ### BlackJackController - -- 게임 전반에서 필요한 I/O 관리 - - 게임 진행 상황 출력 관리 - - 초기 상태 - - 특정 단계에서의 사용자 정보 - - 사용자 이름 입력 관리 - - hit 혹은 stand 여부 입력 관리 - - 게임 결과 출력 관리 \ No newline at end of file +- 입출력(View)과 도메인 모델 간의 데이터 연동 및 전체 게임 흐름 제어 +- 초기 설정(참가자 등록), 베팅 턴 진행, 플레이 턴 진행 단계별 실행 +- 예외 상황 발생 시 에러 메시지를 출력하고, 정상 입력이 들어올 때까지 반복 입력(I/O Retry) 흐름 관리 \ No newline at end of file diff --git a/src/test/java/domain/BlackJackGameTest.java b/src/test/java/domain/BlackJackGameTest.java index c18813da5aa..1021e324659 100644 --- a/src/test/java/domain/BlackJackGameTest.java +++ b/src/test/java/domain/BlackJackGameTest.java @@ -50,12 +50,6 @@ void whosePlayTurn_exist() { assertTrue(result.isPresent()); } - // // - // // public void doBetProcess(String betAmountValue) { - // // Player target = multiPlayers.findNotBetPlayer() - // // .orElseThrow(() -> new IllegalStateException(ErrorMessage.NO_MORE_BETTABLE_PLAYER.getMessage())); - // // multiPlayers.executeBet(target, betAmountValue); - // // } @Test @DisplayName("bet을 시켜도 오류가 안난다") void doBetProcess_success() {