From 311c8416919ecd2fc24b767d0eda3454325ff19b Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Wed, 28 Jan 2026 16:57:26 +0900 Subject: [PATCH 01/22] =?UTF-8?q?[DOCS]:=20gitkeep=ED=8C=8C=EC=9D=BC=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/.gitkeep | 0 1 file changed, 0 insertions(+), 0 deletions(-) delete mode 100644 src/main/java/.gitkeep diff --git a/src/main/java/.gitkeep b/src/main/java/.gitkeep deleted file mode 100644 index e69de29b..00000000 From 3847ffffe689b330b4e7f2847e3dfb1a54f8f204 Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Wed, 28 Jan 2026 16:58:26 +0900 Subject: [PATCH 02/22] =?UTF-8?q?[FEAT]:=20inputView=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/Views/InputView.java | 19 +++++++++++++++++++ 1 file changed, 19 insertions(+) create mode 100644 src/main/java/Views/InputView.java diff --git a/src/main/java/Views/InputView.java b/src/main/java/Views/InputView.java new file mode 100644 index 00000000..e50b21b1 --- /dev/null +++ b/src/main/java/Views/InputView.java @@ -0,0 +1,19 @@ +package Views; + +import java.util.Scanner; + +public class InputView { + private static final Scanner scanner = new Scanner(System.in); + + // 3자리 숫자 입력 받기 + public String readNumbers() { + System.out.print("숫자를 입력해주세요 : "); + return scanner.nextLine(); + } + + // 재시작 또는 종료 버튼 입력 받기 (1 또는 2) + public String readRestartCommand() { + System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."); + return scanner.nextLine(); + } +} From c8f15505b8286e0079adef4ae16db2036ebebc4c Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Wed, 28 Jan 2026 17:12:02 +0900 Subject: [PATCH 03/22] =?UTF-8?q?[FEAT]:=20GameController=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/Controllers/GameController.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) create mode 100644 src/main/java/Controllers/GameController.java diff --git a/src/main/java/Controllers/GameController.java b/src/main/java/Controllers/GameController.java new file mode 100644 index 00000000..597a5f96 --- /dev/null +++ b/src/main/java/Controllers/GameController.java @@ -0,0 +1,13 @@ +package Controllers; + +import Views.InputView; + +public class GameController { + private final InputView inputView; + + public GameController(InputView inputView) { + this.inputView = inputView; + } + + public void run() { } +} \ No newline at end of file From 19255e0315c96a288997ccf1f1c2a1f21bc879b9 Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Wed, 28 Jan 2026 17:33:20 +0900 Subject: [PATCH 04/22] =?UTF-8?q?feat(domain):=20BaseballNumbers=20?= =?UTF-8?q?=EB=8F=84=EB=A9=94=EC=9D=B8=20=ED=81=B4=EB=9E=98=EC=8A=A4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/Domains/BaseballNumbers.java | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/main/java/Domains/BaseballNumbers.java diff --git a/src/main/java/Domains/BaseballNumbers.java b/src/main/java/Domains/BaseballNumbers.java new file mode 100644 index 00000000..e34e25f3 --- /dev/null +++ b/src/main/java/Domains/BaseballNumbers.java @@ -0,0 +1,12 @@ +package Domains; + +import java.util.List; +import java.util.Objects; + +public class BaseballNumbers { + private final List numbers; + + public BaseballNumbers(List numbers) { + this.numbers = numbers; + } +} \ No newline at end of file From d79fc594fdfc5a3f6e9e523726159fa740ce3339 Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Wed, 28 Jan 2026 17:34:55 +0900 Subject: [PATCH 05/22] =?UTF-8?q?feat(domain):=20=EC=A4=91=EB=B3=B5=20?= =?UTF-8?q?=EC=97=86=EB=8A=94=20=EC=88=AB=EC=9E=90=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EA=B8=B0(NumberGenerator)=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1~9 사이의 서로 다른 임의의 숫자 3개를 생성하여 BaseballNumbers를 반환하는 로직 추가 --- src/main/java/Domains/NumberGenerator.java | 24 ++++++++++++++++++++++ 1 file changed, 24 insertions(+) create mode 100644 src/main/java/Domains/NumberGenerator.java diff --git a/src/main/java/Domains/NumberGenerator.java b/src/main/java/Domains/NumberGenerator.java new file mode 100644 index 00000000..a0873ea5 --- /dev/null +++ b/src/main/java/Domains/NumberGenerator.java @@ -0,0 +1,24 @@ +package Domains; + +import java.util.ArrayList; +import java.util.LinkedHashSet; +import java.util.Random; +import java.util.Set; + +public class NumberGenerator { + private static final int NUMBER_COUNT = 3; + private static final int MIN_NUMBER = 1; + private static final int MAX_NUMBER = 9; + + public static BaseballNumbers generate() { + Set uniqueNumbers = new LinkedHashSet<>(); + Random random = new Random(); + + while (uniqueNumbers.size() < NUMBER_COUNT) { + int randomNumber = random.nextInt(MAX_NUMBER - MIN_NUMBER + 1) + MIN_NUMBER; + uniqueNumbers.add(randomNumber); + } + + return new BaseballNumbers(new ArrayList<>(uniqueNumbers)); + } +} \ No newline at end of file From 60ceb8f967af652bbc6393d119918c2db066d98a Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Wed, 28 Jan 2026 17:52:24 +0900 Subject: [PATCH 06/22] =?UTF-8?q?feat(domain):=20=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=ED=81=AC/=EB=B3=BC=20=ED=8C=90=EC=A0=95=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=B0=8F=20GameResult=20=ED=81=B4?= =?UTF-8?q?=EB=9E=98=EC=8A=A4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 두 숫자 리스트를 비교하여 스트라이크와 볼 개수를 계산하는 기능 추가 - 판정 결과를 관리하고 상태를 확인(isThreeStrike, isNothing)하는 GameResult 클래스 추가 --- src/main/java/Domains/BaseballNumbers.java | 23 +++++++++++++++++- src/main/java/Domains/GameResult.java | 28 ++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) create mode 100644 src/main/java/Domains/GameResult.java diff --git a/src/main/java/Domains/BaseballNumbers.java b/src/main/java/Domains/BaseballNumbers.java index e34e25f3..3e1649a0 100644 --- a/src/main/java/Domains/BaseballNumbers.java +++ b/src/main/java/Domains/BaseballNumbers.java @@ -1,7 +1,6 @@ package Domains; import java.util.List; -import java.util.Objects; public class BaseballNumbers { private final List numbers; @@ -9,4 +8,26 @@ public class BaseballNumbers { public BaseballNumbers(List numbers) { this.numbers = numbers; } + + public GameResult compare(List otherNumbers) { + int strikes = 0; + int balls = 0; + + for (int i = 0; i < numbers.size(); i++) { + if (isStrike(i, otherNumbers)) { + strikes++; + } else if (isBall(i, otherNumbers)) { + balls++; + } + } + return new GameResult(strikes, balls); + } + + private boolean isStrike(int index, List otherNumbers) { + return numbers.get(index).equals(otherNumbers.get(index)); + } + + private boolean isBall(int index, List otherNumbers) { + return !isStrike(index, otherNumbers) && numbers.contains(otherNumbers.get(index)); + } } \ No newline at end of file diff --git a/src/main/java/Domains/GameResult.java b/src/main/java/Domains/GameResult.java new file mode 100644 index 00000000..2914c80c --- /dev/null +++ b/src/main/java/Domains/GameResult.java @@ -0,0 +1,28 @@ +package Domains; + +public class GameResult { + private final int strikes; + private final int balls; + + public GameResult(int strikes, int balls) { + this.strikes = strikes; + this.balls = balls; + } + + public boolean isThreeStrike() { + return strikes == 3; + } + + public boolean isNothing() { + return strikes == 0 && balls == 0; + } + + // OutputView에서 사용하기 편하도록 Getter 제공 + public int getStrikes() { + return strikes; + } + + public int getBalls() { + return balls; + } +} \ No newline at end of file From b0efd3d1288da6f99a32da6da69e428ff498d4de Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Wed, 28 Jan 2026 17:54:01 +0900 Subject: [PATCH 07/22] =?UTF-8?q?chore:=20=EB=B6=88=ED=95=84=EC=9A=94?= =?UTF-8?q?=ED=95=B4=EC=A7=84=20.gitkeep=20=ED=8C=8C=EC=9D=BC=20=EC=82=AD?= =?UTF-8?q?=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit test 패키지에 소스 코드가 추가됨에 따라 더 이상 필요 없는 .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 e69de29b..00000000 From a1dcb0d0037e8d9539fe74bd75ad7bab3f4e6742 Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Wed, 28 Jan 2026 17:56:44 +0900 Subject: [PATCH 08/22] =?UTF-8?q?feat(domain):=20BaseballNumbers=20?= =?UTF-8?q?=EC=88=AB=EC=9E=90=20=EB=A6=AC=EC=8A=A4=ED=8A=B8=20=EC=A1=B0?= =?UTF-8?q?=ED=9A=8C=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 외부에서 숫자를 확인할 수 있도록 getNumbers() 메서드 추가 - Collections.unmodifiableList를 적용하여 반환된 리스트의 수정을 방지(불변성 유지) --- src/main/java/Domains/BaseballNumbers.java | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/main/java/Domains/BaseballNumbers.java b/src/main/java/Domains/BaseballNumbers.java index 3e1649a0..28fb585d 100644 --- a/src/main/java/Domains/BaseballNumbers.java +++ b/src/main/java/Domains/BaseballNumbers.java @@ -1,6 +1,7 @@ package Domains; import java.util.List; +import java.util.Collections; public class BaseballNumbers { private final List numbers; @@ -23,6 +24,10 @@ public GameResult compare(List otherNumbers) { return new GameResult(strikes, balls); } + public List getNumbers() { + return Collections.unmodifiableList(numbers); + } + private boolean isStrike(int index, List otherNumbers) { return numbers.get(index).equals(otherNumbers.get(index)); } From b157ad577101ba051484fd48d5273de8210b563c Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Wed, 28 Jan 2026 17:59:29 +0900 Subject: [PATCH 09/22] =?UTF-8?q?test(domain):=20BaseballNumbers=20?= =?UTF-8?q?=ED=8C=90=EC=A0=95=20=EB=A1=9C=EC=A7=81=20=EB=B0=8F=20GameResul?= =?UTF-8?q?t=20=EC=83=81=ED=83=9C=20=EA=B2=80=EC=A6=9D=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 스트라이크, 볼, 낫싱 시나리오별 단위 테스트 작성 - GameResult 클래스의 상태 확인 메서드(isThreeStrike, isNothing) 검증 --- src/test/java/NumberGeneratorTest.java | 42 ++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/test/java/NumberGeneratorTest.java diff --git a/src/test/java/NumberGeneratorTest.java b/src/test/java/NumberGeneratorTest.java new file mode 100644 index 00000000..8adcdd7d --- /dev/null +++ b/src/test/java/NumberGeneratorTest.java @@ -0,0 +1,42 @@ +package domains; // 컨벤션에 따라 소문자로 변경 제안 + +import static org.assertj.core.api.Assertions.*; + +import java.util.List; +import java.util.Set; + +import Domains.BaseballNumbers; +import Domains.NumberGenerator; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.RepeatedTest; +import org.junit.jupiter.api.Test; + +class NumberGeneratorTest { + + @DisplayName("생성된 숫자는 정확히 3개여야 한다.") + @Test + void generateNumberCountTest() { + BaseballNumbers numbers = NumberGenerator.generate(); + assertThat(numbers.getNumbers()).hasSize(3); + } + + @DisplayName("생성된 숫자는 1에서 9 사이의 범위여야 한다.") + @RepeatedTest(100) // 난수 생성이므로 여러 번 반복해서 검증 + void generateNumberRangeTest() { + BaseballNumbers numbers = NumberGenerator.generate(); + + for (int number : numbers.getNumbers()) { + assertThat(number).isBetween(1, 9); + } + } + + @DisplayName("생성된 숫자들 사이에는 중복이 없어야 한다.") + @Test + void generateUniqueNumberTest() { + BaseballNumbers numbers = NumberGenerator.generate(); + List numberList = numbers.getNumbers(); + + // Set에 넣었을 때도 크기가 3이라면 중복이 없는 것 + assertThat(Set.copyOf(numberList)).hasSize(3); + } +} \ No newline at end of file From d587cf5d23e4366cb2300627666e26578ab86486 Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Wed, 28 Jan 2026 18:04:45 +0900 Subject: [PATCH 10/22] =?UTF-8?q?test(domain):=20GameResult=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=ED=99=95=EC=9D=B8=20=EB=A1=9C=EC=A7=81=20=EB=8B=A8?= =?UTF-8?q?=EC=9C=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 3스트라이크 판정 및 낫싱(Nothing) 판정 로직 검증 - 다양한 결과값에 대한 경계값 테스트 수행 --- src/test/java/GameResultTest.java | 37 ++++++++++++++++++++++++++ src/test/java/NumberGeneratorTest.java | 2 +- 2 files changed, 38 insertions(+), 1 deletion(-) create mode 100644 src/test/java/GameResultTest.java diff --git a/src/test/java/GameResultTest.java b/src/test/java/GameResultTest.java new file mode 100644 index 00000000..2246d6d5 --- /dev/null +++ b/src/test/java/GameResultTest.java @@ -0,0 +1,37 @@ +package domains; + +import static org.assertj.core.api.Assertions.*; + +import Domains.GameResult; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +class GameResultTest { + @DisplayName("3 스트라이크일 때 승리 여부를 정확히 판정하는지 확인한다.") + @Test + void isThreeStrike_True_WhenStrikesIsThree() { + GameResult result = new GameResult(3, 0); + assertThat(result.isThreeStrike()).isTrue(); + } + + @DisplayName("스트라이크와 볼이 모두 0이면 '낫싱'으로 판정한다.") + @Test + void isNothing_True_WhenNoStrikeAndNoBall() { + GameResult result = new GameResult(0, 0); + assertThat(result.isNothing()).isTrue(); + } + + @DisplayName("하나라도 맞으면 '낫싱'이 아니다.") + @ParameterizedTest + @CsvSource({ + "1, 0", + "0, 1", + "1, 1" + }) + void isNothing_False_WhenAnyMatch(int strikes, int balls) { + GameResult result = new GameResult(strikes, balls); + assertThat(result.isNothing()).isFalse(); + } +} \ No newline at end of file diff --git a/src/test/java/NumberGeneratorTest.java b/src/test/java/NumberGeneratorTest.java index 8adcdd7d..a28c706d 100644 --- a/src/test/java/NumberGeneratorTest.java +++ b/src/test/java/NumberGeneratorTest.java @@ -1,4 +1,4 @@ -package domains; // 컨벤션에 따라 소문자로 변경 제안 +package domains; import static org.assertj.core.api.Assertions.*; From e213a10ba9bdc5abd690d4605ab3257af6fdab34 Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Wed, 28 Jan 2026 18:10:46 +0900 Subject: [PATCH 11/22] =?UTF-8?q?feat(domain):=20=EC=82=AC=EC=9A=A9?= =?UTF-8?q?=EC=9E=90=20=EC=9E=85=EB=A0=A5=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20?= =?UTF-8?q?=EA=B2=80=EC=A6=9D=EC=9D=84=20=EC=9C=84=ED=95=9C=20Validator=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 게임 숫자 입력 검증(숫자 여부, 3자리, 중복 체크) 추가 - 재시작/종료 명령어(1 또는 2) 검증 추가 - 예외 발생 시 [ERROR] 프리픽스를 포함한 에러 throw --- src/main/java/Domains/Validator.java | 29 ++++++++++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 src/main/java/Domains/Validator.java diff --git a/src/main/java/Domains/Validator.java b/src/main/java/Domains/Validator.java new file mode 100644 index 00000000..854adbac --- /dev/null +++ b/src/main/java/Domains/Validator.java @@ -0,0 +1,29 @@ +package Domains; + +public class Validator { + public static void validateRestartCommand(String input) { + if (!input.equals("1") && !input.equals("2")) { + throw new IllegalArgumentException("[ERROR] 1 또는 2만 입력해야 합니다."); + } + } + + public static void validateInput(String input) { + if (!isNumeric(input)) { + throw new IllegalArgumentException("[ERROR] 숫자만 입력 가능합니다."); + } + if (input.length() != 3) { + throw new IllegalArgumentException("[ERROR] 3자리 숫자여야 합니다."); + } + if (hasDuplicate(input)) { + throw new IllegalArgumentException("[ERROR] 중복된 숫자가 있습니다."); + } + } + + private static boolean isNumeric(String str) { + return str.matches("^[1-9]+$"); // 1-9 사이의 숫자인지 확인 + } + + private static boolean hasDuplicate(String str) { + return str.chars().distinct().count() != str.length(); + } +} \ No newline at end of file From 285428035b5a0d4c2528ff76578a46e25b648e25 Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Wed, 28 Jan 2026 18:15:13 +0900 Subject: [PATCH 12/22] =?UTF-8?q?test(domain):=20Validator=20=EC=9E=85?= =?UTF-8?q?=EB=A0=A5=EA=B0=92=20=EB=B0=8F=20=EC=9E=AC=EC=8B=9C=EC=9E=91=20?= =?UTF-8?q?=EB=AA=85=EB=A0=B9=EC=96=B4=20=EA=B2=80=EC=A6=9D=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 잘못된 형식(문자, 0 포함, 길이 미달/초과, 중복) 입력 시 예외 발생 검증 - 재시작/종료 명령어(1, 2) 외 입력 시 예외 발생 검증 - AssertJ의 assertThatThrownBy를 활용한 에러 메시지 일치 여부 확인 --- src/test/java/ValidatorTest.java | 63 ++++++++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/test/java/ValidatorTest.java diff --git a/src/test/java/ValidatorTest.java b/src/test/java/ValidatorTest.java new file mode 100644 index 00000000..c526af96 --- /dev/null +++ b/src/test/java/ValidatorTest.java @@ -0,0 +1,63 @@ +package domains; + +import static org.assertj.core.api.Assertions.*; + +import Domains.Validator; +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 ValidatorTest { + + @DisplayName("재시작 명령어가 '1' 또는 '2'가 아니면 예외가 발생한다.") + @ParameterizedTest + @ValueSource(strings = {"3", "a", "", " ", "12"}) + void validateRestartCommand_Exception(String input) { + assertThatThrownBy(() -> Validator.validateRestartCommand(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR] 1 또는 2만 입력해야 합니다."); + } + + @DisplayName("정상적인 재시작 명령어인 경우 예외가 발생하지 않는다.") + @ParameterizedTest + @ValueSource(strings = {"1", "2"}) + void validateRestartCommand_Success(String input) { + assertThatCode(() -> Validator.validateRestartCommand(input)) + .doesNotThrowAnyException(); + } + + @DisplayName("숫자 야구 입력값이 1-9 사이의 숫자가 아니거나 문자가 포함되면 예외가 발생한다.") + @ParameterizedTest + @ValueSource(strings = {"012", "a12", "1 2", "1.2", ""}) + void validateInput_NotNumeric_Exception(String input) { + assertThatThrownBy(() -> Validator.validateInput(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR] 숫자만 입력 가능합니다."); + } + + @DisplayName("숫자 야구 입력값이 3자리가 아니면 예외가 발생한다.") + @ParameterizedTest + @ValueSource(strings = {"12", "1234"}) // ""(빈 문자열) 제거 + void validateInput_Length_Exception(String input) { + assertThatThrownBy(() -> Validator.validateInput(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR] 3자리 숫자여야 합니다."); + } + + @DisplayName("숫자 야구 입력값에 중복된 숫자가 있으면 예외가 발생한다.") + @ParameterizedTest + @ValueSource(strings = {"112", "121", "222"}) + void validateInput_Duplicate_Exception(String input) { + assertThatThrownBy(() -> Validator.validateInput(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("[ERROR] 중복된 숫자가 있습니다."); + } + + @DisplayName("올바른 숫자 야구 입력값인 경우 예외가 발생하지 않는다.") + @Test + void validateInput_Success() { + assertThatCode(() -> Validator.validateInput("123")) + .doesNotThrowAnyException(); + } +} \ No newline at end of file From 68af7469c35f5a750a1a36b6ebc5e8b1f1bc25b2 Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Wed, 28 Jan 2026 18:17:27 +0900 Subject: [PATCH 13/22] =?UTF-8?q?feat(controller):=20=EA=B2=8C=EC=9E=84=20?= =?UTF-8?q?=EC=8B=A4=ED=96=89=20=EB=A3=A8=ED=94=84=20=EB=B0=8F=20=EC=9E=AC?= =?UTF-8?q?=EC=8B=9C=EC=9E=91/=EC=A2=85=EB=A3=8C=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - run() 메서드를 통한 전체 게임 흐름 제어 추가 - 사용자의 재시작(1) 및 종료(2) 선택 기능 구현 - 잘못된 입력 시 에러 메시지 출력 후 재입력을 받는 예외 처리 로직 추가 --- src/main/java/Controllers/GameController.java | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/src/main/java/Controllers/GameController.java b/src/main/java/Controllers/GameController.java index 597a5f96..9c25fd88 100644 --- a/src/main/java/Controllers/GameController.java +++ b/src/main/java/Controllers/GameController.java @@ -1,5 +1,6 @@ package Controllers; +import Domains.Validator; import Views.InputView; public class GameController { @@ -9,5 +10,22 @@ public GameController(InputView inputView) { this.inputView = inputView; } - public void run() { } + public void run() { + do { + playGame(); + } while (isRestartRequested()); + } + + private void playGame() { } + + private boolean isRestartRequested() { + try { + String command = inputView.readRestartCommand(); + Validator.validateRestartCommand(command); + return command.equals("1"); + } catch (IllegalArgumentException e) { + System.out.println("[ERROR] " + e.getMessage()); + return isRestartRequested(); // 잘못 입력하면 재귀적으로 다시 물어봄 + } + } } \ No newline at end of file From 9b486fa4cd1bc5f1d5e80bd4a4bb5da361c5efae Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Wed, 28 Jan 2026 18:34:59 +0900 Subject: [PATCH 14/22] =?UTF-8?q?feat(view):=20=EA=B2=8C=EC=9E=84=20?= =?UTF-8?q?=EA=B2=B0=EA=B3=BC=20=EC=B6=9C=EB=A0=A5=EC=9D=84=20=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20OutputView=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 판정 결과(스트라이크, 볼, 낫싱) 포맷팅 및 출력 기능 추가 - 게임 종료 메시지 및 에러 메시지 출력 메서드 구현 - StringBuilder를 사용하여 볼과 스트라이크 개수에 따른 유연한 문자열 생성 --- src/main/java/Views/OutputView.java | 40 +++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) create mode 100644 src/main/java/Views/OutputView.java diff --git a/src/main/java/Views/OutputView.java b/src/main/java/Views/OutputView.java new file mode 100644 index 00000000..ff2697ac --- /dev/null +++ b/src/main/java/Views/OutputView.java @@ -0,0 +1,40 @@ +package Views; + +import Domains.GameResult; + +public class OutputView { + private static final String BALL = "볼"; + private static final String STRIKE = "스트라이크"; + private static final String NOTHING = "낫싱"; + private static final String GAME_END_MESSAGE = "3개의 숫자를 모두 맞히셨습니다! 게임 종료"; + + public void printResult(GameResult result) { + if (result.isNothing()) { + System.out.println(NOTHING); + return; + } + + System.out.println(formatResult(result.getBalls(), result.getStrikes())); + } + + private String formatResult(int balls, int strikes) { + StringBuilder sb = new StringBuilder(); + + if (balls > 0) { + sb.append(balls).append(BALL).append(" "); + } + if (strikes > 0) { + sb.append(strikes).append(STRIKE); + } + + return sb.toString().trim(); + } + + public void printGameEnd() { + System.out.println(GAME_END_MESSAGE); + } + + public void printErrorMessage(String message) { + System.out.println("[ERROR] " + message); + } +} \ No newline at end of file From a534cf2f165166f1f69d74cdcfadb30fce7457b1 Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Wed, 28 Jan 2026 18:36:02 +0900 Subject: [PATCH 15/22] =?UTF-8?q?feat(controller):=20=EC=88=AB=EC=9E=90=20?= =?UTF-8?q?=EC=95=BC=EA=B5=AC=20=EA=B2=8C=EC=9E=84=20=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81(playGame)=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 컴퓨터 난수 생성 및 사용자 입력 반복 루프 구현 - 입력값 유효성 검사 및 예외 발생 시 재입력 유도 로직 추가 - 스트라이크/볼 판정 결과 출력 및 3스트라이크 달성 시 게임 종료 처리 --- src/main/java/Controllers/GameController.java | 45 ++++++++++++++++++- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/main/java/Controllers/GameController.java b/src/main/java/Controllers/GameController.java index 9c25fd88..79fc39aa 100644 --- a/src/main/java/Controllers/GameController.java +++ b/src/main/java/Controllers/GameController.java @@ -1,13 +1,23 @@ package Controllers; +import Domains.BaseballNumbers; +import Domains.GameResult; +import Domains.NumberGenerator; import Domains.Validator; import Views.InputView; +import Views.OutputView; + +import java.util.ArrayList; +import java.util.List; public class GameController { private final InputView inputView; + private final OutputView outputView; // 출력 담당 클래스 (가정) + private BaseballNumbers computerNumbers; - public GameController(InputView inputView) { + public GameController(InputView inputView, OutputView outputView) { this.inputView = inputView; + this.outputView = outputView; } public void run() { @@ -16,7 +26,29 @@ public void run() { } while (isRestartRequested()); } - private void playGame() { } + private void playGame() { + // 1. 컴퓨터 숫자 생성 + computerNumbers = NumberGenerator.generate(); + boolean isGameWon = false; + // 2. 맞출 때까지 반복 + while (!isGameWon) { + try { + String input = inputView.readNumbers(); + Validator.validateInput(input); // 유효성 검사 (실패 시 예외 발생) + + List playerNumbers = parseInput(input); + + GameResult result = computerNumbers.compare(playerNumbers); + outputView.printResult(result); + + isGameWon = result.isThreeStrike(); + } catch (IllegalArgumentException e) { + // 에러 발생 시 [ERROR] 메시지 출력 후 반복문 계속 진행 + System.out.println("[ERROR] " + e.getMessage()); + } + } + outputView.printGameEnd(); + } private boolean isRestartRequested() { try { @@ -28,4 +60,13 @@ private boolean isRestartRequested() { return isRestartRequested(); // 잘못 입력하면 재귀적으로 다시 물어봄 } } + + private List parseInput(String input) { + List numbers = new ArrayList<>(); + for (char c : input.toCharArray()) { + // 문자 '1'을 숫자 1로 변환해서 리스트에 추가 + numbers.add(Character.getNumericValue(c)); + } + return numbers; + } } \ No newline at end of file From 4e4357391725be82e02d0e63ad76f12736e69f7f Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Wed, 28 Jan 2026 18:36:40 +0900 Subject: [PATCH 16/22] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=EA=B7=B8?= =?UTF-8?q?=EB=9E=A8=20=EC=8B=9C=EC=9E=91=EC=A0=90(Application)=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84=20=EB=B0=8F=20=EC=9D=98=EC=A1=B4=EC=84=B1=20?= =?UTF-8?q?=EC=A3=BC=EC=9E=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - main 메서드에서 InputView, OutputView, GameController 인스턴스 생성 - 컨트롤러에 뷰 의존성 주입 및 run() 호출을 통한 프로그램 실행 로직 완성 --- src/main/java/Application.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/main/java/Application.java diff --git a/src/main/java/Application.java b/src/main/java/Application.java new file mode 100644 index 00000000..adbdc8e5 --- /dev/null +++ b/src/main/java/Application.java @@ -0,0 +1,14 @@ +import Controllers.GameController; +import Views.InputView; +import Views.OutputView; + +public class Application { + public static void main(String[] args) { + InputView inputView = new InputView(); + OutputView outputView = new OutputView(); + + GameController controller = new GameController(inputView, outputView); + + controller.run(); + } +} \ No newline at end of file From 2541784e54fee29f47b9ec1ad8862d056315d84e Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Sun, 1 Feb 2026 22:20:26 +0900 Subject: [PATCH 17/22] =?UTF-8?q?[REFACT]:=20output=EC=9D=98=20message=20p?= =?UTF-8?q?rint=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/Controllers/GameController.java | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/main/java/Controllers/GameController.java b/src/main/java/Controllers/GameController.java index 79fc39aa..006075a8 100644 --- a/src/main/java/Controllers/GameController.java +++ b/src/main/java/Controllers/GameController.java @@ -43,8 +43,7 @@ private void playGame() { isGameWon = result.isThreeStrike(); } catch (IllegalArgumentException e) { - // 에러 발생 시 [ERROR] 메시지 출력 후 반복문 계속 진행 - System.out.println("[ERROR] " + e.getMessage()); + outputView.printErrorMessage(e.getMessage()); } } outputView.printGameEnd(); From d4fc0d0f7afba0eb6e4f8785143be620a75061ec Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Mon, 2 Feb 2026 11:35:08 +0900 Subject: [PATCH 18/22] =?UTF-8?q?[STYLE]:=20package=20=EB=84=A4=EC=9E=84?= =?UTF-8?q?=20=EB=8C=80=EB=AC=B8=EC=9E=90=EC=97=90=EC=84=9C=20=EC=86=8C?= =?UTF-8?q?=EB=AC=B8=EC=9E=90=EB=A1=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/Application.java | 6 +++--- .../GameController.java | 14 +++++++------- .../java/{Domains => domains}/BaseballNumbers.java | 2 +- src/main/java/{Domains => domains}/GameResult.java | 2 +- .../java/{Domains => domains}/NumberGenerator.java | 2 +- src/main/java/{Domains => domains}/Validator.java | 2 +- src/main/java/{Views => views}/InputView.java | 2 +- src/main/java/{Views => views}/OutputView.java | 4 ++-- src/test/java/GameResultTest.java | 1 - src/test/java/NumberGeneratorTest.java | 2 -- src/test/java/ValidatorTest.java | 1 - 11 files changed, 17 insertions(+), 21 deletions(-) rename src/main/java/{Controllers => controllers}/GameController.java (91%) rename src/main/java/{Domains => domains}/BaseballNumbers.java (98%) rename src/main/java/{Domains => domains}/GameResult.java (96%) rename src/main/java/{Domains => domains}/NumberGenerator.java (97%) rename src/main/java/{Domains => domains}/Validator.java (98%) rename src/main/java/{Views => views}/InputView.java (97%) rename src/main/java/{Views => views}/OutputView.java (96%) diff --git a/src/main/java/Application.java b/src/main/java/Application.java index adbdc8e5..745f7829 100644 --- a/src/main/java/Application.java +++ b/src/main/java/Application.java @@ -1,6 +1,6 @@ -import Controllers.GameController; -import Views.InputView; -import Views.OutputView; +import controllers.GameController; +import views.InputView; +import views.OutputView; public class Application { public static void main(String[] args) { diff --git a/src/main/java/Controllers/GameController.java b/src/main/java/controllers/GameController.java similarity index 91% rename from src/main/java/Controllers/GameController.java rename to src/main/java/controllers/GameController.java index 006075a8..54221955 100644 --- a/src/main/java/Controllers/GameController.java +++ b/src/main/java/controllers/GameController.java @@ -1,11 +1,11 @@ -package Controllers; +package controllers; -import Domains.BaseballNumbers; -import Domains.GameResult; -import Domains.NumberGenerator; -import Domains.Validator; -import Views.InputView; -import Views.OutputView; +import domains.BaseballNumbers; +import domains.GameResult; +import domains.NumberGenerator; +import domains.Validator; +import views.InputView; +import views.OutputView; import java.util.ArrayList; import java.util.List; diff --git a/src/main/java/Domains/BaseballNumbers.java b/src/main/java/domains/BaseballNumbers.java similarity index 98% rename from src/main/java/Domains/BaseballNumbers.java rename to src/main/java/domains/BaseballNumbers.java index 28fb585d..43560156 100644 --- a/src/main/java/Domains/BaseballNumbers.java +++ b/src/main/java/domains/BaseballNumbers.java @@ -1,4 +1,4 @@ -package Domains; +package domains; import java.util.List; import java.util.Collections; diff --git a/src/main/java/Domains/GameResult.java b/src/main/java/domains/GameResult.java similarity index 96% rename from src/main/java/Domains/GameResult.java rename to src/main/java/domains/GameResult.java index 2914c80c..46eb69b3 100644 --- a/src/main/java/Domains/GameResult.java +++ b/src/main/java/domains/GameResult.java @@ -1,4 +1,4 @@ -package Domains; +package domains; public class GameResult { private final int strikes; diff --git a/src/main/java/Domains/NumberGenerator.java b/src/main/java/domains/NumberGenerator.java similarity index 97% rename from src/main/java/Domains/NumberGenerator.java rename to src/main/java/domains/NumberGenerator.java index a0873ea5..3636ab37 100644 --- a/src/main/java/Domains/NumberGenerator.java +++ b/src/main/java/domains/NumberGenerator.java @@ -1,4 +1,4 @@ -package Domains; +package domains; import java.util.ArrayList; import java.util.LinkedHashSet; diff --git a/src/main/java/Domains/Validator.java b/src/main/java/domains/Validator.java similarity index 98% rename from src/main/java/Domains/Validator.java rename to src/main/java/domains/Validator.java index 854adbac..1b3df7c6 100644 --- a/src/main/java/Domains/Validator.java +++ b/src/main/java/domains/Validator.java @@ -1,4 +1,4 @@ -package Domains; +package domains; public class Validator { public static void validateRestartCommand(String input) { diff --git a/src/main/java/Views/InputView.java b/src/main/java/views/InputView.java similarity index 97% rename from src/main/java/Views/InputView.java rename to src/main/java/views/InputView.java index e50b21b1..9a3508fb 100644 --- a/src/main/java/Views/InputView.java +++ b/src/main/java/views/InputView.java @@ -1,4 +1,4 @@ -package Views; +package views; import java.util.Scanner; diff --git a/src/main/java/Views/OutputView.java b/src/main/java/views/OutputView.java similarity index 96% rename from src/main/java/Views/OutputView.java rename to src/main/java/views/OutputView.java index ff2697ac..55dc1a73 100644 --- a/src/main/java/Views/OutputView.java +++ b/src/main/java/views/OutputView.java @@ -1,6 +1,6 @@ -package Views; +package views; -import Domains.GameResult; +import domains.GameResult; public class OutputView { private static final String BALL = "볼"; diff --git a/src/test/java/GameResultTest.java b/src/test/java/GameResultTest.java index 2246d6d5..e2e2a790 100644 --- a/src/test/java/GameResultTest.java +++ b/src/test/java/GameResultTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.*; -import Domains.GameResult; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; diff --git a/src/test/java/NumberGeneratorTest.java b/src/test/java/NumberGeneratorTest.java index a28c706d..291b79b6 100644 --- a/src/test/java/NumberGeneratorTest.java +++ b/src/test/java/NumberGeneratorTest.java @@ -5,8 +5,6 @@ import java.util.List; import java.util.Set; -import Domains.BaseballNumbers; -import Domains.NumberGenerator; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.RepeatedTest; import org.junit.jupiter.api.Test; diff --git a/src/test/java/ValidatorTest.java b/src/test/java/ValidatorTest.java index c526af96..347f18fe 100644 --- a/src/test/java/ValidatorTest.java +++ b/src/test/java/ValidatorTest.java @@ -2,7 +2,6 @@ import static org.assertj.core.api.Assertions.*; -import Domains.Validator; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.params.ParameterizedTest; From 3b7ab08d7c53519146f8093204a32002ee9d94b0 Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Mon, 2 Feb 2026 11:35:26 +0900 Subject: [PATCH 19/22] =?UTF-8?q?[STYLE]:=20else=20=EB=AC=B8=20=EC=A0=9C?= =?UTF-8?q?=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domains/BaseballNumbers.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/domains/BaseballNumbers.java b/src/main/java/domains/BaseballNumbers.java index 43560156..8d76e7c3 100644 --- a/src/main/java/domains/BaseballNumbers.java +++ b/src/main/java/domains/BaseballNumbers.java @@ -17,7 +17,11 @@ public GameResult compare(List otherNumbers) { for (int i = 0; i < numbers.size(); i++) { if (isStrike(i, otherNumbers)) { strikes++; - } else if (isBall(i, otherNumbers)) { + } + } + + for (int i = 0; i < numbers.size(); i++) { + if (isBall(i, otherNumbers)) { balls++; } } From 1a287c5b98b8e71d79fe06691e570c1597e9371d Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Mon, 2 Feb 2026 11:35:50 +0900 Subject: [PATCH 20/22] =?UTF-8?q?[STYLE]:=20Validator=EC=97=90=EC=84=9C=20?= =?UTF-8?q?stream=20api=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domains/Validator.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/main/java/domains/Validator.java b/src/main/java/domains/Validator.java index 1b3df7c6..300d15d4 100644 --- a/src/main/java/domains/Validator.java +++ b/src/main/java/domains/Validator.java @@ -1,5 +1,8 @@ package domains; +import java.util.HashSet; +import java.util.Set; + public class Validator { public static void validateRestartCommand(String input) { if (!input.equals("1") && !input.equals("2")) { @@ -24,6 +27,10 @@ private static boolean isNumeric(String str) { } private static boolean hasDuplicate(String str) { - return str.chars().distinct().count() != str.length(); + Set uniqueChars = new HashSet<>(); + for (char c : str.toCharArray()) { + uniqueChars.add(c); + } + return uniqueChars.size() != str.length(); } } \ No newline at end of file From a064b5763c998a146f4f01183d3fb3513f3beb94 Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Mon, 2 Feb 2026 11:40:21 +0900 Subject: [PATCH 21/22] =?UTF-8?q?[STYLE]:=20=EB=A9=94=EC=84=9C=EB=93=9C=20?= =?UTF-8?q?=EA=B8=B8=EC=9D=B4=EA=B0=80=20=EA=B8=B8=EC=96=B4=EC=A0=B8?= =?UTF-8?q?=EC=84=9C=20=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/controllers/GameController.java | 27 ++++++++++--------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/main/java/controllers/GameController.java b/src/main/java/controllers/GameController.java index 54221955..4f1fffe4 100644 --- a/src/main/java/controllers/GameController.java +++ b/src/main/java/controllers/GameController.java @@ -32,23 +32,24 @@ private void playGame() { boolean isGameWon = false; // 2. 맞출 때까지 반복 while (!isGameWon) { - try { - String input = inputView.readNumbers(); - Validator.validateInput(input); // 유효성 검사 (실패 시 예외 발생) - - List playerNumbers = parseInput(input); - - GameResult result = computerNumbers.compare(playerNumbers); - outputView.printResult(result); - - isGameWon = result.isThreeStrike(); - } catch (IllegalArgumentException e) { - outputView.printErrorMessage(e.getMessage()); - } + isGameWon = playTurn(computerNumbers); // 루프 내부 로직 분리 (15라인 제한 준수) } outputView.printGameEnd(); } + private boolean playTurn(BaseballNumbers computerNumbers) { + try { + String input = inputView.readNumbers(); + Validator.validateInput(input); + GameResult result = computerNumbers.compare(parseInput(input)); + outputView.printResult(result); + return result.isThreeStrike(); + } catch (IllegalArgumentException e) { + outputView.printErrorMessage(e.getMessage()); // UI 일관성 유지 + return false; + } + } + private boolean isRestartRequested() { try { String command = inputView.readRestartCommand(); From 9c392c9cdae5992a7d8e7d3efd6de47392deba59 Mon Sep 17 00:00:00 2001 From: "rui.bao" Date: Mon, 2 Feb 2026 11:53:22 +0900 Subject: [PATCH 22/22] =?UTF-8?q?docs:=20=EA=B8=B0=EB=8A=A5=20=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=EC=82=AC=ED=95=AD=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d7e8aee..b748ab1a 100644 --- a/README.md +++ b/README.md @@ -1 +1,26 @@ -# java-baseball-precourse \ No newline at end of file +# java-baseball-precourse + +1. [domains] Baseball 정답 생성기 +- 1에서 9까지 서로 다른 임의의 수 3개를 선택하여 저장한다. + +2. [domains] Baseball 판독기 (핵심 로직) +- 플레이어가 입력한 수와 컴퓨터의 수를 비교한다. + +- 같은 수가 같은 자리에 있으면 스트라이크 개수를 센다. + +- 같은 수가 다른 자리에 있으면 볼 개수를 센다. + +- 두 숫자의 비교 결과를 GameResult와 같은 객체로 반환한다. + +3. [controllers] Baseball 게임 진행 +- 3 스트라이크인 경우 게임 종료를 판정한다. +- 게임 종료 후 재시작(1) 또는 완전히 종료(2) 여부를 판단한다. + +4. [validator] 입력기 및 에러 핸들러 +- 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨다. +- 숫자만 입력했는지, 3자리인지, 중복이 없는지 검증한다. +- 예외 발생 시 [ERROR]로 시작하는 메시지를 출력하고 게임을 지속한다. + +5. [View] 출력기 +- 입력한 숫자에 대한 결과(볼, 스트라이크, 낫싱)를 형식에 맞춰 출력한다. +- 게임 종료 시와 에러 시에 적절한 메시지를 화면에 출력한다.