From d8490a3bf0130d27676ca03992dfea32d09ab9da Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Wed, 10 Dec 2025 19:33:14 +0900 Subject: [PATCH 1/9] =?UTF-8?q?docs(README):=20=EA=B5=AC=ED=98=84=ED=95=A0?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EB=AA=A9=EB=A1=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 174 ++++++++++++++++++------------------------------------ 1 file changed, 57 insertions(+), 117 deletions(-) diff --git a/README.md b/README.md index e7af80b4ff..3be8e6610c 100644 --- a/README.md +++ b/README.md @@ -1,91 +1,69 @@ -# 미션 - 숫자 야구 - -## 🔍 진행 방식 - -- 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있다. -- 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만든다. -- 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다. - -## 📮 미션 제출 방법 - -- 미션 구현을 완료한 후 GitHub을 통해 제출해야 한다. - - GitHub을 활용한 제출 방법은 [프리코스 과제 제출](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 문서를 참고해 - 제출한다. -- GitHub에 미션을 제출한 후 [우아한테크코스 지원](https://apply.techcourse.co.kr) 사이트에 접속하여 프리코스 과제를 제출한다. - - 자세한 방법은 [제출 가이드](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse#제출-가이드) 참고 - - **Pull Request만 보내고 지원 플랫폼에서 과제를 제출하지 않으면 최종 제출하지 않은 것으로 처리되니 주의한다.** - -## 🚨 과제 제출 전 체크 리스트 - 0점 방지 - -- 기능 구현을 모두 정상적으로 했더라도 **요구 사항에 명시된 출력값 형식을 지키지 않을 경우 0점으로 처리**한다. -- 기능 구현을 완료한 뒤 아래 가이드에 따라 테스트를 실행했을 때 모든 테스트가 성공하는지 확인한다. -- **테스트가 실패할 경우 0점으로 처리**되므로, 반드시 확인 후 제출한다. - -### 테스트 실행 가이드 - -- 터미널에서 `java -version`을 실행하여 Java 버전이 17인지 확인한다. - Eclipse 또는 IntelliJ IDEA와 같은 IDE에서 Java 17로 실행되는지 확인한다. -- 터미널에서 Mac 또는 Linux 사용자의 경우 `./gradlew clean test` 명령을 실행하고, - Windows 사용자의 경우 `gradlew.bat clean test` 또는 `./gradlew.bat clean test` 명령을 실행할 때 모든 테스트가 아래와 같이 통과하는지 확인한다. - -``` -BUILD SUCCESSFUL in 0s -``` +우아한테크코스 웹 백엔드 최종 코딩테스트 대비를 위한 연습용 프로젝트: 숫자 야구 --- -## 🚀 기능 요구 사항 - -기본적으로 1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임이다. - -- 같은 수가 같은 자리에 있으면 스트라이크, 다른 자리에 있으면 볼, 같은 수가 전혀 없으면 낫싱이란 힌트를 얻고, 그 힌트를 이용해서 먼저 상대방(컴퓨터)의 수를 맞추면 승리한다. - - 예) 상대방(컴퓨터)의 수가 425일 때 - - 123을 제시한 경우 : 1스트라이크 - - 456을 제시한 경우 : 1볼 1스트라이크 - - 789를 제시한 경우 : 낫싱 -- 위 숫자 야구 게임에서 상대방의 역할을 컴퓨터가 한다. 컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다. 게임 플레이어는 컴퓨터가 생각하고 있는 서로 다른 3개의 숫자를 입력하고, 컴퓨터는 입력한 숫자에 대한 - 결과를 출력한다. -- 이 같은 과정을 반복해 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임이 종료된다. -- 게임을 종료한 후 게임을 다시 시작하거나 완전히 종료할 수 있다. -- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시킨 후 애플리케이션은 종료되어야 한다. - -### 입출력 요구 사항 - -#### 입력 - -- 서로 다른 3자리의 수 -- 게임이 끝난 경우 재시작/종료를 구분하는 1과 2 중 하나의 수 - -#### 출력 - -- 입력한 수에 대한 결과를 볼, 스트라이크 개수로 표시 - -``` -1볼 1스트라이크 -``` - -- 하나도 없는 경우 - -``` -낫싱 -``` +## 🎯 프로젝트 개요 -- 3개의 숫자를 모두 맞힐 경우 +우아한테크코스 최종 코딩테스트를 대비하기 위한 연습용 프로젝트입니다. +숫자 야구 게임 프로그램을 구현합니다. -``` -3스트라이크 -3개의 숫자를 모두 맞히셨습니다! 게임 종료 -``` +--- -- 게임 시작 문구 출력 +## 📝 구현 기능 목록 + +### 1. 게임 시작 +- [ ] **게임 시작 안내** + - 시작 안내 문구 출력 (형식: `숫자 야구 게임을 시작합니다.`) +- [ ] **정답 숫자 설정** + - 1 ~ 9 중 서로 다른 랜덤한 숫자 3개 선정 + +### 2. 게임 진행 +- [ ] **정답 후보 숫자 입력** + - 입력 안내 문구 출력 (형식: `숫자를 입력해주세요 : `) + - 사용자가 정답으로 유추되는 숫자를 입력 + - 1 ~ 9 사이 세 자리의 겹치지 않는 숫자 입력 + - **(예외 처리)** 사용자 정답 유효성 검사: `IllegalArgumentException` (형식: `[ERROR] {에러메시지 내용}`) + - 입력 값이 존재하지 않는 경우: 빈 문자열, 공백 문자열, Null + - 숫자로 변환이 불가한 경우: 숫자가 아닌 문자 포함 + - 입력한 숫자가 세 자리가 아닌 경우: 입력한 숫자의 개수가 3개 미만 또는 초과 + - 입력한 숫자가 1 ~ 9 의 양수가 아닌 경우: 입력한 숫자가 1 ~ 9 범위에서 벗어남 + - 예외 발생 시, 게임 종료 + - 사용자가 입력한 정답 후보 숫자 저장 +- [ ] **일치하는 숫자 결과 확인** + - 같은 수가 존재하는 경우 + - 같은 수가 같은 자리에 있는 경우 카운트 + - 같은 수가 다른 자리에 있는 경우 카운트 + - 같은 수가 전혀 없는 경우: 낫싱 +- [ ] **정답 입력 시도 결과 출력** + - 일치 여부 결과 출력 + - 같은 수가 존재하는 경우 (형식: `{일치 개수}{경우의 종류}`, 여러 개라면 한칸 띄우고 이어서 작성) + - 같은 수가 같은 자리에 있는 경우: 스트라이크 + - 같은 수가 다른 자리에 있는 경우: 볼 + - 같은 수가 전혀 없는 경우 (형식: `낫싱`) + - 정답을 맞추지 못한 경우 게임 진행 프로세스 다시 시작 + - 정답을 맞춘 경우, 게임 종료 프로세스 진행 + +### 3. 게임 종료 +- [ ] **게임 종료 안내 출력** (형식: `3개의 숫자를 모두 맞히셨습니다! 게임 종료`) + - 3스트라이크로 3개의 숫자를 모두 맞춘 경우 게임 종료 +- [ ] **새 게임 진행 여부 결정** + - 게임 진행 안내 문구 출력 (형식: `게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.`) + - 사용자가 게임의 진행을 결정하는 숫자를 입력 + - 1 또는 2를 입력 + - **(예외 처리)** 사용자 답변 유효성 검사: `IllegalArgumentException` (형식: `[ERROR] {에러메시지 내용}`) + - 입력 값이 존재하지 않는 경우: 빈 문자열, 공백 문자열, Null + - 숫자로 변환이 불가한 경우: 숫자가 아닌 문자 포함 + - 입력한 숫자가 1 또는 2가 아닌 경우: 입력한 숫자가 1, 2 이외의 숫자인 경우 + - 예외 발생 시, 게임 종료 + - 사용자가 입력한 숫자에 따라 새 게임 진행 여부 판별 + - 입력한 숫자가 1일 경우: 게임 시작 프로세스 다시 시작. 게임 시작 안내 문구는 생략. + - 입력한 숫자가 2일 경우: 게임 종료 -``` -숫자 야구 게임을 시작합니다. -``` +--- -#### 실행 결과 예시 +## 🖨️ 실행 결과 예시 -``` +```prolog 숫자 야구 게임을 시작합니다. 숫자를 입력해주세요 : 123 1볼 1스트라이크 @@ -104,41 +82,3 @@ BUILD SUCCESSFUL in 0s 1볼 ... ``` - ---- - -## 🎯 프로그래밍 요구 사항 - -- JDK 17 버전에서 실행 가능해야 한다. **JDK 17에서 정상적으로 동작하지 않을 경우 0점 처리한다.** -- 프로그램 실행의 시작점은 `Application`의 `main()`이다. -- `build.gradle` 파일을 변경할 수 없고, 외부 라이브러리를 사용하지 않는다. -- [Java 코드 컨벤션](https://github.com/woowacourse/woowacourse-docs/tree/master/styleguide/java) 가이드를 준수하며 프로그래밍한다. -- 프로그램 종료 시 `System.exit()`를 호출하지 않는다. -- 프로그램 구현이 완료되면 `ApplicationTest`의 모든 테스트가 성공해야 한다. **테스트가 실패할 경우 0점 처리한다.** -- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 이름을 수정하거나 이동하지 않는다. - -### 라이브러리 - -- `camp.nextstep.edu.missionutils`에서 제공하는 `Randoms` 및 `Console` API를 사용하여 구현해야 한다. - - Random 값 추출은 `camp.nextstep.edu.missionutils.Randoms`의 `pickNumberInRange()`를 활용한다. - - 사용자가 입력하는 값은 `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 활용한다. - -#### 사용 예시 - -```java -List computer = new ArrayList<>(); -while (computer.size() < 3) { - int randomNumber = Randoms.pickNumberInRange(1, 9); - if (!computer.contains(randomNumber)) { - computer.add(randomNumber); - } -} -``` - ---- - -## ✏️ 과제 진행 요구 사항 - -- 미션은 [java-baseball-6](https://github.com/woowacourse-precourse/java-baseball-6) 저장소를 Fork & Clone해 시작한다. -- **기능을 구현하기 전 `docs/README.md`에 구현할 기능 목록을 정리**해 추가한다. -- 과제 진행 및 제출 방법은 [프리코스 과제 제출](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 문서를 참고한다. From c8442fdc902ca78a8af1eba5d2350c75cc4e352a Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Wed, 10 Dec 2025 19:52:03 +0900 Subject: [PATCH 2/9] =?UTF-8?q?feat:=20[=EA=B2=8C=EC=9E=84=20=EC=8B=9C?= =?UTF-8?q?=EC=9E=91]=20=EA=B2=8C=EC=9E=84=20=EC=8B=9C=EC=9E=91=20?= =?UTF-8?q?=EC=95=88=EB=82=B4=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 --- README.md | 2 +- src/main/java/baseball/Application.java | 11 +++++++- src/main/java/baseball/config/AppConfig.java | 28 +++++++++++++++++++ .../baseball/controller/GameController.java | 18 ++++++++++++ .../java/baseball/view/ConsoleOutputView.java | 13 +++++++++ src/main/java/baseball/view/OutputView.java | 8 ++++++ 6 files changed, 78 insertions(+), 2 deletions(-) create mode 100644 src/main/java/baseball/config/AppConfig.java create mode 100644 src/main/java/baseball/controller/GameController.java create mode 100644 src/main/java/baseball/view/ConsoleOutputView.java create mode 100644 src/main/java/baseball/view/OutputView.java diff --git a/README.md b/README.md index 3be8e6610c..a7dd9ce816 100644 --- a/README.md +++ b/README.md @@ -12,7 +12,7 @@ ## 📝 구현 기능 목록 ### 1. 게임 시작 -- [ ] **게임 시작 안내** +- [x] **게임 시작 안내** - 시작 안내 문구 출력 (형식: `숫자 야구 게임을 시작합니다.`) - [ ] **정답 숫자 설정** - 1 ~ 9 중 서로 다른 랜덤한 숫자 3개 선정 diff --git a/src/main/java/baseball/Application.java b/src/main/java/baseball/Application.java index dd95a34214..41de07fe59 100644 --- a/src/main/java/baseball/Application.java +++ b/src/main/java/baseball/Application.java @@ -1,7 +1,16 @@ package baseball; +import baseball.config.AppConfig; +import baseball.controller.GameController; + +/** + * 프로그램 진입점을 담당하는 클래스 + */ public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + AppConfig appConfig = AppConfig.getInstance(); + + GameController controller = appConfig.GameController(); + controller.run(); } } diff --git a/src/main/java/baseball/config/AppConfig.java b/src/main/java/baseball/config/AppConfig.java new file mode 100644 index 0000000000..d4b765107b --- /dev/null +++ b/src/main/java/baseball/config/AppConfig.java @@ -0,0 +1,28 @@ +package baseball.config; + +import baseball.controller.GameController; +import baseball.view.ConsoleOutputView; +import baseball.view.OutputView; + +/** + * 애플리케이션의 실행에 필요한 모든 객체를 생성하고 서로 연결하는 설정 클래스 + */ +public class AppConfig { + private static class LazyHolder { + public static final AppConfig INSTANCE = new AppConfig(); + + public static final OutputView OUTPUT_VIEW = new ConsoleOutputView(); + + public static final GameController CONTROLLER = new GameController(OUTPUT_VIEW); + } + + private AppConfig() {} + + public static AppConfig getInstance() { + return LazyHolder.INSTANCE; + } + + public GameController GameController() { + return LazyHolder.CONTROLLER; + } +} diff --git a/src/main/java/baseball/controller/GameController.java b/src/main/java/baseball/controller/GameController.java new file mode 100644 index 0000000000..1c5a421fc0 --- /dev/null +++ b/src/main/java/baseball/controller/GameController.java @@ -0,0 +1,18 @@ +package baseball.controller; + +import baseball.view.OutputView; + +/** + * 프로그램의 전체 흐름을 조율하는 클래스 + */ +public class GameController { + private final OutputView outputView; + + public GameController(OutputView outputView) { + this.outputView = outputView; + }; + + public void run() { + outputView.printGameStartInstruction(); + } +} diff --git a/src/main/java/baseball/view/ConsoleOutputView.java b/src/main/java/baseball/view/ConsoleOutputView.java new file mode 100644 index 0000000000..17bdeaaf73 --- /dev/null +++ b/src/main/java/baseball/view/ConsoleOutputView.java @@ -0,0 +1,13 @@ +package baseball.view; + +/** + * 프로그램의 콘솔 출력을 담당하는 클래스 + */ +public class ConsoleOutputView implements OutputView { + private static final String GAME_START_INSTRUCTION = "숫자 야구 게임을 시작합니다."; + + @Override + public void printGameStartInstruction() { + System.out.println(GAME_START_INSTRUCTION); + }; +} diff --git a/src/main/java/baseball/view/OutputView.java b/src/main/java/baseball/view/OutputView.java new file mode 100644 index 0000000000..4c6ed40620 --- /dev/null +++ b/src/main/java/baseball/view/OutputView.java @@ -0,0 +1,8 @@ +package baseball.view; + +/** + * 프로그램의 모든 출력을 담당하는 인터페이스 + */ +public interface OutputView { + default void printGameStartInstruction() {}; +} \ No newline at end of file From 14774c0cfac426686cb5205eecfed91b4f3f51ea Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Wed, 10 Dec 2025 20:55:49 +0900 Subject: [PATCH 3/9] =?UTF-8?q?feat:=20[=EA=B2=8C=EC=9E=84=20=EC=8B=9C?= =?UTF-8?q?=EC=9E=91]=20=EC=A0=95=EB=8B=B5=20=EC=88=AB=EC=9E=90=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=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 --- README.md | 3 +- src/main/java/baseball/config/AppConfig.java | 8 +++- .../baseball/controller/GameController.java | 7 ++- .../java/baseball/domain/BaseballNumber.java | 40 +++++++++++++++++ src/main/java/baseball/domain/Game.java | 12 +++++ .../java/baseball/exception/ErrorMessage.java | 22 +++++++++ .../baseball/service/BaseballService.java | 28 ++++++++++++ .../java/baseball/util/NumberGenerator.java | 7 +++ .../baseball/util/RandomNumberGenerator.java | 19 ++++++++ .../baseball/domain/BaseballNumberTest.java | 45 +++++++++++++++++++ .../baseball/service/BaseballServiceTest.java | 28 ++++++++++++ 11 files changed, 216 insertions(+), 3 deletions(-) create mode 100644 src/main/java/baseball/domain/BaseballNumber.java create mode 100644 src/main/java/baseball/domain/Game.java create mode 100644 src/main/java/baseball/exception/ErrorMessage.java create mode 100644 src/main/java/baseball/service/BaseballService.java create mode 100644 src/main/java/baseball/util/NumberGenerator.java create mode 100644 src/main/java/baseball/util/RandomNumberGenerator.java create mode 100644 src/test/java/baseball/domain/BaseballNumberTest.java create mode 100644 src/test/java/baseball/service/BaseballServiceTest.java diff --git a/README.md b/README.md index a7dd9ce816..cb11b234a6 100644 --- a/README.md +++ b/README.md @@ -14,8 +14,9 @@ ### 1. 게임 시작 - [x] **게임 시작 안내** - 시작 안내 문구 출력 (형식: `숫자 야구 게임을 시작합니다.`) -- [ ] **정답 숫자 설정** +- [x] **정답 숫자 설정** - 1 ~ 9 중 서로 다른 랜덤한 숫자 3개 선정 + - 선정된 정답 숫자 저장 ### 2. 게임 진행 - [ ] **정답 후보 숫자 입력** diff --git a/src/main/java/baseball/config/AppConfig.java b/src/main/java/baseball/config/AppConfig.java index d4b765107b..8e51beb821 100644 --- a/src/main/java/baseball/config/AppConfig.java +++ b/src/main/java/baseball/config/AppConfig.java @@ -1,6 +1,9 @@ package baseball.config; import baseball.controller.GameController; +import baseball.service.BaseballService; +import baseball.util.NumberGenerator; +import baseball.util.RandomNumberGenerator; import baseball.view.ConsoleOutputView; import baseball.view.OutputView; @@ -13,7 +16,10 @@ private static class LazyHolder { public static final OutputView OUTPUT_VIEW = new ConsoleOutputView(); - public static final GameController CONTROLLER = new GameController(OUTPUT_VIEW); + public static final NumberGenerator NUMBER_GENERATOR = new RandomNumberGenerator(); + public static final BaseballService SERVICE = new BaseballService(NUMBER_GENERATOR); + + public static final GameController CONTROLLER = new GameController(OUTPUT_VIEW, SERVICE); } private AppConfig() {} diff --git a/src/main/java/baseball/controller/GameController.java b/src/main/java/baseball/controller/GameController.java index 1c5a421fc0..738222c461 100644 --- a/src/main/java/baseball/controller/GameController.java +++ b/src/main/java/baseball/controller/GameController.java @@ -1,5 +1,7 @@ package baseball.controller; +import baseball.domain.Game; +import baseball.service.BaseballService; import baseball.view.OutputView; /** @@ -7,12 +9,15 @@ */ public class GameController { private final OutputView outputView; + private final BaseballService service; - public GameController(OutputView outputView) { + public GameController(OutputView outputView, BaseballService service) { this.outputView = outputView; + this.service = service; }; public void run() { outputView.printGameStartInstruction(); + Game game = service.createGame(); } } diff --git a/src/main/java/baseball/domain/BaseballNumber.java b/src/main/java/baseball/domain/BaseballNumber.java new file mode 100644 index 0000000000..1858a6d968 --- /dev/null +++ b/src/main/java/baseball/domain/BaseballNumber.java @@ -0,0 +1,40 @@ +package baseball.domain; + +import baseball.exception.ErrorMessage; + +import java.util.List; + +/** + * 숫자 야구 게임에 사용되는 숫자 도메인 클래스 + */ +public class BaseballNumber { + public static int NUMBER_SIZE = 3; + public static int NUMBER_MIN = 1; + public static int NUMBER_MAX = 9; + + private final List numbers; + + public BaseballNumber(List numbers) { + validate(numbers); + this.numbers = numbers; + } + + private void validate(List numbers) { + validateSize(numbers); + validateRange(numbers); + } + + private void validateSize(List numbers) { + if (numbers.size() != 3) { + throw new IllegalArgumentException(ErrorMessage.INVALID_NUMBER_SIZE.getMessage()); + } + } + + private void validateRange(List numbers) { + for (int number : numbers) { + if (number < NUMBER_MIN || number > NUMBER_MAX) { + throw new IllegalArgumentException(ErrorMessage.NUMBER_OUT_OF_RANGE.getMessage()); + } + } + } +} diff --git a/src/main/java/baseball/domain/Game.java b/src/main/java/baseball/domain/Game.java new file mode 100644 index 0000000000..0bd91cb02a --- /dev/null +++ b/src/main/java/baseball/domain/Game.java @@ -0,0 +1,12 @@ +package baseball.domain; + +/** + * 게임 상태 관리를 담당하는 클래스 + */ +public class Game { + private final BaseballNumber answer; + + public Game(BaseballNumber answer) { + this.answer = answer; + } +} diff --git a/src/main/java/baseball/exception/ErrorMessage.java b/src/main/java/baseball/exception/ErrorMessage.java new file mode 100644 index 0000000000..c4f0a7ee25 --- /dev/null +++ b/src/main/java/baseball/exception/ErrorMessage.java @@ -0,0 +1,22 @@ +package baseball.exception; + +/** + * 오류 메시지를 정의한 enum 클래스 + */ +public enum ErrorMessage { + // BaseballNumber Error + INVALID_NUMBER_SIZE("숫자는 세자리여야 합니다."), + NUMBER_OUT_OF_RANGE("각 자리의 숫자는 1~9 사이의 정수여야 합니다."); + + private static final String ERROR_PREFIX = "[ERROR] "; + + private final String message; + + ErrorMessage(String message) { + this.message = message; + } + + public String getMessage() { + return ERROR_PREFIX + message; + } +} \ No newline at end of file diff --git a/src/main/java/baseball/service/BaseballService.java b/src/main/java/baseball/service/BaseballService.java new file mode 100644 index 0000000000..4f9c7c84ed --- /dev/null +++ b/src/main/java/baseball/service/BaseballService.java @@ -0,0 +1,28 @@ +package baseball.service; + +import baseball.domain.Game; +import baseball.domain.BaseballNumber; +import baseball.util.NumberGenerator; + +import java.util.List; + +import static baseball.domain.BaseballNumber.NUMBER_SIZE; +import static baseball.domain.BaseballNumber.NUMBER_MIN; +import static baseball.domain.BaseballNumber.NUMBER_MAX; + +/** + * 숫자 야구 게임 비즈니스 로직을 담당하는 클래스 + */ +public class BaseballService { + private final NumberGenerator numberGenerator; + + public BaseballService(NumberGenerator numberGenerator) { + this.numberGenerator = numberGenerator; + } + + public Game createGame() { + List numbers = numberGenerator.generateUniqueNumbersInRange(NUMBER_SIZE, NUMBER_MIN, NUMBER_MAX); + BaseballNumber answer = new BaseballNumber(numbers); + return new Game(answer); + } +} diff --git a/src/main/java/baseball/util/NumberGenerator.java b/src/main/java/baseball/util/NumberGenerator.java new file mode 100644 index 0000000000..04d3323249 --- /dev/null +++ b/src/main/java/baseball/util/NumberGenerator.java @@ -0,0 +1,7 @@ +package baseball.util; + +import java.util.List; + +public interface NumberGenerator { + List generateUniqueNumbersInRange(int size, int start, int end); +} diff --git a/src/main/java/baseball/util/RandomNumberGenerator.java b/src/main/java/baseball/util/RandomNumberGenerator.java new file mode 100644 index 0000000000..3a1e1db507 --- /dev/null +++ b/src/main/java/baseball/util/RandomNumberGenerator.java @@ -0,0 +1,19 @@ +package baseball.util; + +import camp.nextstep.edu.missionutils.Randoms; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class RandomNumberGenerator implements NumberGenerator { + @Override + public List generateUniqueNumbersInRange(int size, int start, int end) { + Set numbers = new HashSet<>(); + while (numbers.size() < size) { + numbers.add(Randoms.pickNumberInRange(start, end)); + } + + return numbers.stream().toList(); + } +} diff --git a/src/test/java/baseball/domain/BaseballNumberTest.java b/src/test/java/baseball/domain/BaseballNumberTest.java new file mode 100644 index 0000000000..fbe294fc66 --- /dev/null +++ b/src/test/java/baseball/domain/BaseballNumberTest.java @@ -0,0 +1,45 @@ +package baseball.domain; + +import baseball.exception.ErrorMessage; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class BaseballNumberTest { + @Nested + class SuccessTest { + @DisplayName("유효한 숫자가 주어지면 정상적으로 BaseballNumber 객체를 생성한다") + @Test + void should_Return_BaseballNumber() { + // when & then + assertThat(new BaseballNumber(List.of(1,2,3))).isNotNull() + .isInstanceOf(BaseballNumber.class); + } + } + + @Nested + class exceptionTest { + @DisplayName("유효하지 않은 개수의 숫자가 주어지면 예외를 발생시킨다") + @Test + void should_ThrowException_ForInvalidNumber() { + // when & then + assertThatThrownBy(() -> new BaseballNumber(List.of(1,2,3,4))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ErrorMessage.INVALID_NUMBER_SIZE.getMessage()); + } + + @DisplayName("유효하지 않은 범위의 숫자가 주어지면 예외를 발생시킨다") + @Test + void should_ThrowException_ForNumberOutOfRange() { + // when & then + assertThatThrownBy(() -> new BaseballNumber(List.of(0,1,2))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ErrorMessage.NUMBER_OUT_OF_RANGE.getMessage()); + } + } +} diff --git a/src/test/java/baseball/service/BaseballServiceTest.java b/src/test/java/baseball/service/BaseballServiceTest.java new file mode 100644 index 0000000000..5099f22cd3 --- /dev/null +++ b/src/test/java/baseball/service/BaseballServiceTest.java @@ -0,0 +1,28 @@ +package baseball.service; + +import baseball.domain.Game; +import baseball.util.NumberGenerator; +import baseball.util.RandomNumberGenerator; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class BaseballServiceTest { + @Nested + class SuccessTest { + @DisplayName("게임을 정상적으로 생성한다") + @Test + void should_Return_Game() { + // given + NumberGenerator numberGenerator = new RandomNumberGenerator(); + BaseballService service = new BaseballService(numberGenerator); + + // when & then + assertThat(service.createGame()).isNotNull() + .isInstanceOf(Game.class); + } + } +} From ab5e78e07eca0e95ae94d1e2441caf49b655b2df Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Wed, 10 Dec 2025 21:53:32 +0900 Subject: [PATCH 4/9] =?UTF-8?q?feat:=20[=EA=B2=8C=EC=9E=84=20=EC=A7=84?= =?UTF-8?q?=ED=96=89]=20=EC=A0=95=EB=8B=B5=20=ED=9B=84=EB=B3=B4=20?= =?UTF-8?q?=EC=88=AB=EC=9E=90=20=EC=9E=85=EB=A0=A5=20=EA=B8=B0=EB=8A=A5=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 --- README.md | 3 +- src/main/java/baseball/config/AppConfig.java | 5 +- .../baseball/controller/GameController.java | 8 ++- .../java/baseball/domain/BaseballNumber.java | 33 +++++++++ .../java/baseball/exception/ErrorMessage.java | 7 +- .../exception/InputNotNumericException.java | 10 +++ .../exception/InputNullOrBlankException.java | 10 +++ .../InputNumberOverflowException.java | 10 +++ .../baseball/service/BaseballService.java | 4 ++ src/main/java/baseball/util/InputParser.java | 42 +++++++++++ .../java/baseball/view/ConsoleInputView.java | 13 ++++ .../java/baseball/view/ConsoleOutputView.java | 9 ++- src/main/java/baseball/view/InputView.java | 10 +++ src/main/java/baseball/view/OutputView.java | 2 + .../baseball/domain/BaseballNumberTest.java | 44 ++++++++++++ .../java/baseball/util/InputParserTest.java | 69 +++++++++++++++++++ 16 files changed, 271 insertions(+), 8 deletions(-) create mode 100644 src/main/java/baseball/exception/InputNotNumericException.java create mode 100644 src/main/java/baseball/exception/InputNullOrBlankException.java create mode 100644 src/main/java/baseball/exception/InputNumberOverflowException.java create mode 100644 src/main/java/baseball/util/InputParser.java create mode 100644 src/main/java/baseball/view/ConsoleInputView.java create mode 100644 src/main/java/baseball/view/InputView.java create mode 100644 src/test/java/baseball/util/InputParserTest.java diff --git a/README.md b/README.md index cb11b234a6..bd9f8050bd 100644 --- a/README.md +++ b/README.md @@ -19,7 +19,7 @@ - 선정된 정답 숫자 저장 ### 2. 게임 진행 -- [ ] **정답 후보 숫자 입력** +- [x] **정답 후보 숫자 입력** - 입력 안내 문구 출력 (형식: `숫자를 입력해주세요 : `) - 사용자가 정답으로 유추되는 숫자를 입력 - 1 ~ 9 사이 세 자리의 겹치지 않는 숫자 입력 @@ -28,6 +28,7 @@ - 숫자로 변환이 불가한 경우: 숫자가 아닌 문자 포함 - 입력한 숫자가 세 자리가 아닌 경우: 입력한 숫자의 개수가 3개 미만 또는 초과 - 입력한 숫자가 1 ~ 9 의 양수가 아닌 경우: 입력한 숫자가 1 ~ 9 범위에서 벗어남 + - 입력한 숫자 중 중복되는 숫자가 존재하는 경우: 세 숫자 중 겹치는 숫자가 존재함 - 예외 발생 시, 게임 종료 - 사용자가 입력한 정답 후보 숫자 저장 - [ ] **일치하는 숫자 결과 확인** diff --git a/src/main/java/baseball/config/AppConfig.java b/src/main/java/baseball/config/AppConfig.java index 8e51beb821..a7f8acc8e4 100644 --- a/src/main/java/baseball/config/AppConfig.java +++ b/src/main/java/baseball/config/AppConfig.java @@ -4,7 +4,9 @@ import baseball.service.BaseballService; import baseball.util.NumberGenerator; import baseball.util.RandomNumberGenerator; +import baseball.view.ConsoleInputView; import baseball.view.ConsoleOutputView; +import baseball.view.InputView; import baseball.view.OutputView; /** @@ -14,12 +16,13 @@ public class AppConfig { private static class LazyHolder { public static final AppConfig INSTANCE = new AppConfig(); + public static final InputView INPUT_VIEW = new ConsoleInputView(); public static final OutputView OUTPUT_VIEW = new ConsoleOutputView(); public static final NumberGenerator NUMBER_GENERATOR = new RandomNumberGenerator(); public static final BaseballService SERVICE = new BaseballService(NUMBER_GENERATOR); - public static final GameController CONTROLLER = new GameController(OUTPUT_VIEW, SERVICE); + public static final GameController CONTROLLER = new GameController(INPUT_VIEW, OUTPUT_VIEW, SERVICE); } private AppConfig() {} diff --git a/src/main/java/baseball/controller/GameController.java b/src/main/java/baseball/controller/GameController.java index 738222c461..df537a385e 100644 --- a/src/main/java/baseball/controller/GameController.java +++ b/src/main/java/baseball/controller/GameController.java @@ -2,16 +2,19 @@ import baseball.domain.Game; import baseball.service.BaseballService; +import baseball.view.InputView; import baseball.view.OutputView; /** * 프로그램의 전체 흐름을 조율하는 클래스 */ public class GameController { + private final InputView inputView; private final OutputView outputView; private final BaseballService service; - public GameController(OutputView outputView, BaseballService service) { + public GameController(InputView inputView, OutputView outputView, BaseballService service) { + this.inputView = inputView; this.outputView = outputView; this.service = service; }; @@ -19,5 +22,8 @@ public GameController(OutputView outputView, BaseballService service) { public void run() { outputView.printGameStartInstruction(); Game game = service.createGame(); + + String guessNumber = inputView.readGuessingNumber(); + service.match(guessNumber); } } diff --git a/src/main/java/baseball/domain/BaseballNumber.java b/src/main/java/baseball/domain/BaseballNumber.java index 1858a6d968..d06d763a59 100644 --- a/src/main/java/baseball/domain/BaseballNumber.java +++ b/src/main/java/baseball/domain/BaseballNumber.java @@ -1,8 +1,15 @@ package baseball.domain; import baseball.exception.ErrorMessage; +import baseball.exception.InputNotNumericException; +import baseball.exception.InputNullOrBlankException; +import baseball.exception.InputNumberOverflowException; +import baseball.util.InputParser; +import java.util.ArrayList; +import java.util.HashSet; import java.util.List; +import java.util.Set; /** * 숫자 야구 게임에 사용되는 숫자 도메인 클래스 @@ -19,9 +26,26 @@ public BaseballNumber(List numbers) { this.numbers = numbers; } + public static BaseballNumber from(String input) { + List numbers = new ArrayList<>(); + for (String numberString : input.split("")) { + try { + numbers.add(InputParser.parseToInt(numberString)); + } catch (InputNullOrBlankException e) { + throw new IllegalArgumentException(ErrorMessage.NUMBER_NULL_OR_BLANK.getMessage()); + } catch (InputNotNumericException e) { + throw new IllegalArgumentException(ErrorMessage.NUMBER_NOT_NUMERIC.getMessage()); + } catch (InputNumberOverflowException e) { + throw new IllegalArgumentException(ErrorMessage.INVALID_NUMBER_SIZE.getMessage()); + } + } + return new BaseballNumber(numbers); + } + private void validate(List numbers) { validateSize(numbers); validateRange(numbers); + validateDuplication(numbers); } private void validateSize(List numbers) { @@ -37,4 +61,13 @@ private void validateRange(List numbers) { } } } + + private void validateDuplication(List numbers) { + Set uniqueNumbers = new HashSet<>(); + for (int number : numbers) { + if (!uniqueNumbers.add(number)) { + throw new IllegalArgumentException(ErrorMessage.NUMBER_DUPLICATED.getMessage()); + } + } + } } diff --git a/src/main/java/baseball/exception/ErrorMessage.java b/src/main/java/baseball/exception/ErrorMessage.java index c4f0a7ee25..064db1df05 100644 --- a/src/main/java/baseball/exception/ErrorMessage.java +++ b/src/main/java/baseball/exception/ErrorMessage.java @@ -6,7 +6,10 @@ public enum ErrorMessage { // BaseballNumber Error INVALID_NUMBER_SIZE("숫자는 세자리여야 합니다."), - NUMBER_OUT_OF_RANGE("각 자리의 숫자는 1~9 사이의 정수여야 합니다."); + NUMBER_OUT_OF_RANGE("각 자리의 숫자는 1~9 사이의 정수여야 합니다."), + NUMBER_NULL_OR_BLANK("숫자는 비워두거나 공백을 포함할 수 없습니다."), + NUMBER_NOT_NUMERIC("숫자가 아닌 문자를 포함할 수 없습니다."), + NUMBER_DUPLICATED("세자리의 숫자는 서로 중복될 수 없습니다."); private static final String ERROR_PREFIX = "[ERROR] "; @@ -19,4 +22,4 @@ public enum ErrorMessage { public String getMessage() { return ERROR_PREFIX + message; } -} \ No newline at end of file +} diff --git a/src/main/java/baseball/exception/InputNotNumericException.java b/src/main/java/baseball/exception/InputNotNumericException.java new file mode 100644 index 0000000000..56bea0d75c --- /dev/null +++ b/src/main/java/baseball/exception/InputNotNumericException.java @@ -0,0 +1,10 @@ +package baseball.exception; + +/** + * 입력값이 숫자가 아닐 경우 발생하는 예외 클래스 + */ +public class InputNotNumericException extends RuntimeException { + public InputNotNumericException() { + super(); + } +} diff --git a/src/main/java/baseball/exception/InputNullOrBlankException.java b/src/main/java/baseball/exception/InputNullOrBlankException.java new file mode 100644 index 0000000000..fe8b0a1e43 --- /dev/null +++ b/src/main/java/baseball/exception/InputNullOrBlankException.java @@ -0,0 +1,10 @@ +package baseball.exception; + +/** + * 입력값이 null이거나, 비어 있거나, 공백 문자로만 이루어진 경우 발생하는 예외 클래스 + */ +public class InputNullOrBlankException extends RuntimeException { + public InputNullOrBlankException() { + super(); + } +} diff --git a/src/main/java/baseball/exception/InputNumberOverflowException.java b/src/main/java/baseball/exception/InputNumberOverflowException.java new file mode 100644 index 0000000000..b72e645892 --- /dev/null +++ b/src/main/java/baseball/exception/InputNumberOverflowException.java @@ -0,0 +1,10 @@ +package baseball.exception; + +/** + * 입력한 값이 유효한 범위를 초과한 경우 발생하는 예외 클래스 + */ +public class InputNumberOverflowException extends RuntimeException { + public InputNumberOverflowException() { + super(); + } +} diff --git a/src/main/java/baseball/service/BaseballService.java b/src/main/java/baseball/service/BaseballService.java index 4f9c7c84ed..5de1931411 100644 --- a/src/main/java/baseball/service/BaseballService.java +++ b/src/main/java/baseball/service/BaseballService.java @@ -25,4 +25,8 @@ public Game createGame() { BaseballNumber answer = new BaseballNumber(numbers); return new Game(answer); } + + public void match(String guessString) { + BaseballNumber guessNumber = BaseballNumber.from(guessString); + } } diff --git a/src/main/java/baseball/util/InputParser.java b/src/main/java/baseball/util/InputParser.java new file mode 100644 index 0000000000..44637ee91f --- /dev/null +++ b/src/main/java/baseball/util/InputParser.java @@ -0,0 +1,42 @@ +package baseball.util; + +import baseball.exception.InputNotNumericException; +import baseball.exception.InputNullOrBlankException; +import baseball.exception.InputNumberOverflowException; + +import java.math.BigInteger; +import java.util.ArrayList; +import java.util.List; + +/** + * 사용자 입력을 변환, 검증하는 유틸리티 클래스 + */ +public final class InputParser { + private InputParser() {} + + public static String refineInput(String input) { + boolean isNullOrBlank = (input == null) || input.isBlank(); + if (isNullOrBlank) { + throw new InputNullOrBlankException(); + } + return input.trim(); + } + + public static int parseToInt(String input) { + String refinedInput = refineInput(input); + try { + return Integer.parseInt(refinedInput); + } catch (NumberFormatException e) { + distinguishNumberFormatError(input); + throw new InputNumberOverflowException(); + } + } + + private static void distinguishNumberFormatError(String input) { + try { + new BigInteger(input); + } catch (NumberFormatException e) { + throw new InputNotNumericException(); + } + } +} \ No newline at end of file diff --git a/src/main/java/baseball/view/ConsoleInputView.java b/src/main/java/baseball/view/ConsoleInputView.java new file mode 100644 index 0000000000..41926d8412 --- /dev/null +++ b/src/main/java/baseball/view/ConsoleInputView.java @@ -0,0 +1,13 @@ +package baseball.view; + +import camp.nextstep.edu.missionutils.Console; + +/** + * 프로그램의 콘솔 입력을 담당하는 클래스 + */ +public class ConsoleInputView implements InputView { + @Override + public String readGuessingNumber() { + return Console.readLine(); + } +} \ No newline at end of file diff --git a/src/main/java/baseball/view/ConsoleOutputView.java b/src/main/java/baseball/view/ConsoleOutputView.java index 17bdeaaf73..aa0a09320b 100644 --- a/src/main/java/baseball/view/ConsoleOutputView.java +++ b/src/main/java/baseball/view/ConsoleOutputView.java @@ -4,10 +4,13 @@ * 프로그램의 콘솔 출력을 담당하는 클래스 */ public class ConsoleOutputView implements OutputView { - private static final String GAME_START_INSTRUCTION = "숫자 야구 게임을 시작합니다."; - @Override public void printGameStartInstruction() { - System.out.println(GAME_START_INSTRUCTION); + System.out.println("숫자 야구 게임을 시작합니다."); }; + + @Override + public void printNumberInputPrompt() { + System.out.print("숫자를 입력해주세요 : "); + } } diff --git a/src/main/java/baseball/view/InputView.java b/src/main/java/baseball/view/InputView.java new file mode 100644 index 0000000000..ef863306ea --- /dev/null +++ b/src/main/java/baseball/view/InputView.java @@ -0,0 +1,10 @@ +package baseball.view; + +/** + * 프로그램의 모든 입력을 담당하는 인터페이스 + */ +public interface InputView { + default String readGuessingNumber() { + return null; + }; +} \ No newline at end of file diff --git a/src/main/java/baseball/view/OutputView.java b/src/main/java/baseball/view/OutputView.java index 4c6ed40620..affd0f909c 100644 --- a/src/main/java/baseball/view/OutputView.java +++ b/src/main/java/baseball/view/OutputView.java @@ -5,4 +5,6 @@ */ public interface OutputView { default void printGameStartInstruction() {}; + + default void printNumberInputPrompt() {}; } \ No newline at end of file diff --git a/src/test/java/baseball/domain/BaseballNumberTest.java b/src/test/java/baseball/domain/BaseballNumberTest.java index fbe294fc66..fc07d77d5b 100644 --- a/src/test/java/baseball/domain/BaseballNumberTest.java +++ b/src/test/java/baseball/domain/BaseballNumberTest.java @@ -4,6 +4,8 @@ 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; import java.util.List; @@ -19,11 +21,33 @@ void should_Return_BaseballNumber() { // when & then assertThat(new BaseballNumber(List.of(1,2,3))).isNotNull() .isInstanceOf(BaseballNumber.class); + assertThat(BaseballNumber.from("123")).isNotNull() + .isInstanceOf(BaseballNumber.class); } } @Nested class exceptionTest { + @DisplayName("숫자가 비어있거나 공백을 포함하는 경우 예외를 발생시킨다") + @ParameterizedTest + @ValueSource(strings = {" ", "", "1 23"}) + void should_ThrowException_WhenNumberNullOrBlank(String input) { + // when & then + assertThatThrownBy(() -> BaseballNumber.from(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ErrorMessage.NUMBER_NULL_OR_BLANK.getMessage()); + } + + @DisplayName("숫자가 아닌 문자를 포함하는 경우 예외를 발생시킨다") + @ParameterizedTest + @ValueSource(strings = {"1d5", "g23", "123a"}) + void should_ThrowException_WhenNumberNotNumeric(String input) { + // when & then + assertThatThrownBy(() -> BaseballNumber.from(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ErrorMessage.NUMBER_NOT_NUMERIC.getMessage()); + } + @DisplayName("유효하지 않은 개수의 숫자가 주어지면 예외를 발생시킨다") @Test void should_ThrowException_ForInvalidNumber() { @@ -31,6 +55,10 @@ void should_ThrowException_ForInvalidNumber() { assertThatThrownBy(() -> new BaseballNumber(List.of(1,2,3,4))) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining(ErrorMessage.INVALID_NUMBER_SIZE.getMessage()); + + assertThatThrownBy(() -> BaseballNumber.from("1234765")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ErrorMessage.INVALID_NUMBER_SIZE.getMessage()); } @DisplayName("유효하지 않은 범위의 숫자가 주어지면 예외를 발생시킨다") @@ -40,6 +68,22 @@ void should_ThrowException_ForNumberOutOfRange() { assertThatThrownBy(() -> new BaseballNumber(List.of(0,1,2))) .isInstanceOf(IllegalArgumentException.class) .hasMessageContaining(ErrorMessage.NUMBER_OUT_OF_RANGE.getMessage()); + + assertThatThrownBy(() -> BaseballNumber.from("012")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ErrorMessage.NUMBER_OUT_OF_RANGE.getMessage()); + } + + @DisplayName("중복된 숫자가 포함된 경우 예외를 발생시킨다") + @Test + void should_ThrowException_ForDuplicatedNumbers() { + // when & then + assertThatThrownBy(() -> new BaseballNumber(List.of(1,1,2))) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ErrorMessage.NUMBER_DUPLICATED.getMessage()); + assertThatThrownBy(() -> BaseballNumber.from("112")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining(ErrorMessage.NUMBER_DUPLICATED.getMessage()); } } } diff --git a/src/test/java/baseball/util/InputParserTest.java b/src/test/java/baseball/util/InputParserTest.java new file mode 100644 index 0000000000..44dd56bb0e --- /dev/null +++ b/src/test/java/baseball/util/InputParserTest.java @@ -0,0 +1,69 @@ +package baseball.util; + +import baseball.exception.InputNotNumericException; +import baseball.exception.InputNullOrBlankException; +import baseball.exception.InputNumberOverflowException; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Nested; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; +import org.junit.jupiter.params.provider.EmptySource; +import org.junit.jupiter.params.provider.NullSource; +import org.junit.jupiter.params.provider.ValueSource; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +public class InputParserTest { + @Nested + class SuccessTest { + @DisplayName("양 옆의 공백을 제거한 문자열을 반환한다.") + @ParameterizedTest + @CsvSource(value = {" 가나디 ,가나디", " 먀오,먀오", "헤이 ,헤이"}) + void should_ReturnTrimmedString(String input, String expected) { + // when & then + assertThat(InputParser.refineInput(input)).isEqualTo(expected); + } + + @DisplayName("문자열을 숫자로 변환한다.") + @ParameterizedTest + @CsvSource(value = {" 1 ,1", " 2,2", "3 ,3", "4,4"}) + void should_ReturnConvertedIntValue(String input, int expected) { + // when & then + assertThat(InputParser.parseToInt(input)).isEqualTo(expected); + } + } + + @Nested + class ExceptionTest { + @DisplayName("Null, 빈 문자열, 공백 문자열을 입력받을 경우 예외가 발생한다.") + @ParameterizedTest + @NullSource + @EmptySource + @ValueSource(strings = " ") + void should_ThrowException_ForNullOfEmptyOrBlank(String input) { + assertThatThrownBy(() -> InputParser.refineInput(input)) + .isInstanceOf(InputNullOrBlankException.class); + } + + @DisplayName("숫자로 변환 불가한 문자열을 입력받을 경우 예외가 발생한다.") + @ParameterizedTest + @ValueSource(strings = {"3천원", "3k", "$1000", "3,000", "3000.00", "3 000"}) + void should_ThrowException_When_NotConvertibleToNumeric(String input) { + // when & then + assertThatThrownBy(() -> InputParser.parseToInt(input)) + .isInstanceOf(InputNotNumericException.class); + } + + @DisplayName("Integer의 범위를 초과하는 문자열을 입력받을 경우 예외가 발생한다.") + @ParameterizedTest + @ValueSource(strings = {"999999999999999999999999999999", Integer.MAX_VALUE + 1L + ""}) + void should_ThrowException_When_IntegerOverflow(String input) { + // when & then + assertThatThrownBy(() -> InputParser.parseToInt(input)) + .isInstanceOf(InputNumberOverflowException.class); + } + } + + +} From 74c259f01cb06b569bb3ada63c3466b7896403d0 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Wed, 10 Dec 2025 21:55:42 +0900 Subject: [PATCH 5/9] =?UTF-8?q?feat:=20[=EA=B2=8C=EC=9E=84=20=EC=A7=84?= =?UTF-8?q?=ED=96=89]=20=EC=A0=95=EB=8B=B5=20=ED=9B=84=EB=B3=B4=20?= =?UTF-8?q?=EC=88=AB=EC=9E=90=20=EC=9E=85=EB=A0=A5=20=EC=95=88=EB=82=B4=20?= =?UTF-8?q?=EB=AC=B8=EA=B5=AC=20=EC=B6=9C=EB=A0=A5=20=EA=B8=B0=EB=8A=A5=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/baseball/controller/GameController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/baseball/controller/GameController.java b/src/main/java/baseball/controller/GameController.java index df537a385e..cd944186a0 100644 --- a/src/main/java/baseball/controller/GameController.java +++ b/src/main/java/baseball/controller/GameController.java @@ -23,6 +23,7 @@ public void run() { outputView.printGameStartInstruction(); Game game = service.createGame(); + outputView.printNumberInputPrompt(); String guessNumber = inputView.readGuessingNumber(); service.match(guessNumber); } From 8cdfecc7862adb96b533831f2543b5ee79b5f82c Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Wed, 10 Dec 2025 22:41:30 +0900 Subject: [PATCH 6/9] =?UTF-8?q?feat:=20[=EA=B2=8C=EC=9E=84=20=EC=A7=84?= =?UTF-8?q?=ED=96=89]=20=EC=9D=BC=EC=B9=98=ED=95=98=EB=8A=94=20=EC=88=AB?= =?UTF-8?q?=EC=9E=90=20=EA=B2=B0=EA=B3=BC=20=ED=99=95=EC=9D=B8=20=EA=B8=B0?= =?UTF-8?q?=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 --- README.md | 2 +- .../baseball/controller/GameController.java | 3 +- .../java/baseball/domain/BaseballNumber.java | 21 +++++++++++++ src/main/java/baseball/domain/Game.java | 6 ++++ src/main/java/baseball/domain/Match.java | 30 +++++++++++++++++++ src/main/java/baseball/domain/Matches.java | 24 +++++++++++++++ .../exception/NoMatchingNumberException.java | 7 +++++ .../baseball/service/BaseballService.java | 6 +++- .../baseball/service/BaseballServiceTest.java | 27 +++++++++++++++++ 9 files changed, 123 insertions(+), 3 deletions(-) create mode 100644 src/main/java/baseball/domain/Match.java create mode 100644 src/main/java/baseball/domain/Matches.java create mode 100644 src/main/java/baseball/exception/NoMatchingNumberException.java diff --git a/README.md b/README.md index bd9f8050bd..6999e0ed63 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ - 입력한 숫자 중 중복되는 숫자가 존재하는 경우: 세 숫자 중 겹치는 숫자가 존재함 - 예외 발생 시, 게임 종료 - 사용자가 입력한 정답 후보 숫자 저장 -- [ ] **일치하는 숫자 결과 확인** +- [x] **일치하는 숫자 결과 확인** - 같은 수가 존재하는 경우 - 같은 수가 같은 자리에 있는 경우 카운트 - 같은 수가 다른 자리에 있는 경우 카운트 diff --git a/src/main/java/baseball/controller/GameController.java b/src/main/java/baseball/controller/GameController.java index cd944186a0..d74c6e8a46 100644 --- a/src/main/java/baseball/controller/GameController.java +++ b/src/main/java/baseball/controller/GameController.java @@ -1,6 +1,7 @@ package baseball.controller; import baseball.domain.Game; +import baseball.domain.Matches; import baseball.service.BaseballService; import baseball.view.InputView; import baseball.view.OutputView; @@ -25,6 +26,6 @@ public void run() { outputView.printNumberInputPrompt(); String guessNumber = inputView.readGuessingNumber(); - service.match(guessNumber); + Matches matchResult = service.match(game, guessNumber); } } diff --git a/src/main/java/baseball/domain/BaseballNumber.java b/src/main/java/baseball/domain/BaseballNumber.java index d06d763a59..55010a817d 100644 --- a/src/main/java/baseball/domain/BaseballNumber.java +++ b/src/main/java/baseball/domain/BaseballNumber.java @@ -42,6 +42,27 @@ public static BaseballNumber from(String input) { return new BaseballNumber(numbers); } + public List match(BaseballNumber guess) { + List matches = new ArrayList<>(); + for (int i = 0; i < NUMBER_SIZE; i++) { + int number = numbers.get(i); + boolean hasMatchingNumber = guess.contains(number); + boolean hasCorrectMatchIndex = guess.get(i) == number; + if (hasMatchingNumber) { + matches.add(Match.of(hasMatchingNumber, hasCorrectMatchIndex)); + } + } + return matches; + } + + private boolean contains(int number) { + return numbers.contains(number); + } + + private int get(int index) { + return numbers.get(index); + } + private void validate(List numbers) { validateSize(numbers); validateRange(numbers); diff --git a/src/main/java/baseball/domain/Game.java b/src/main/java/baseball/domain/Game.java index 0bd91cb02a..963195883a 100644 --- a/src/main/java/baseball/domain/Game.java +++ b/src/main/java/baseball/domain/Game.java @@ -1,5 +1,7 @@ package baseball.domain; +import java.util.List; + /** * 게임 상태 관리를 담당하는 클래스 */ @@ -9,4 +11,8 @@ public class Game { public Game(BaseballNumber answer) { this.answer = answer; } + + public List matchWithAnswer(BaseballNumber number) { + return answer.match(number); + } } diff --git a/src/main/java/baseball/domain/Match.java b/src/main/java/baseball/domain/Match.java new file mode 100644 index 0000000000..4acd2a2458 --- /dev/null +++ b/src/main/java/baseball/domain/Match.java @@ -0,0 +1,30 @@ +package baseball.domain; + +import baseball.exception.NoMatchingNumberException; + +public enum Match { + STRIKE("스트라이크"), + BALL("볼"); + + private final String message; + + Match(String message) { + this.message = message; + } + + public static Match of(boolean hasMatchingNumber, boolean hasCorrectMatchIndex) { + if (hasMatchingNumber && hasCorrectMatchIndex) { + return STRIKE; + } + + if (hasMatchingNumber) { + return BALL; + } + + throw new NoMatchingNumberException(); + } + + public String getMessage() { + return message; + } +} diff --git a/src/main/java/baseball/domain/Matches.java b/src/main/java/baseball/domain/Matches.java new file mode 100644 index 0000000000..e7a7b02f19 --- /dev/null +++ b/src/main/java/baseball/domain/Matches.java @@ -0,0 +1,24 @@ +package baseball.domain; + +import java.util.EnumMap; +import java.util.List; + +public class Matches { + private final EnumMap matchCount; + + public Matches(EnumMap matchCount) { + this.matchCount = matchCount; + } + + public static Matches from(List matches) { + EnumMap matchCount = new EnumMap<>(Match.class); + for (Match match : matches) { + matchCount.merge(match, 1, Integer::sum); + } + return new Matches(matchCount); + } + + public int getMatchCount(Match match) { + return matchCount.getOrDefault(match, 0); + } +} diff --git a/src/main/java/baseball/exception/NoMatchingNumberException.java b/src/main/java/baseball/exception/NoMatchingNumberException.java new file mode 100644 index 0000000000..508e66509f --- /dev/null +++ b/src/main/java/baseball/exception/NoMatchingNumberException.java @@ -0,0 +1,7 @@ +package baseball.exception; + +public class NoMatchingNumberException extends RuntimeException { + public NoMatchingNumberException() { + super(); + } +} diff --git a/src/main/java/baseball/service/BaseballService.java b/src/main/java/baseball/service/BaseballService.java index 5de1931411..35d1ee7fd5 100644 --- a/src/main/java/baseball/service/BaseballService.java +++ b/src/main/java/baseball/service/BaseballService.java @@ -2,6 +2,8 @@ import baseball.domain.Game; import baseball.domain.BaseballNumber; +import baseball.domain.Match; +import baseball.domain.Matches; import baseball.util.NumberGenerator; import java.util.List; @@ -26,7 +28,9 @@ public Game createGame() { return new Game(answer); } - public void match(String guessString) { + public Matches match(Game game, String guessString) { BaseballNumber guessNumber = BaseballNumber.from(guessString); + List matches = game.matchWithAnswer(guessNumber); + return Matches.from(matches); } } diff --git a/src/test/java/baseball/service/BaseballServiceTest.java b/src/test/java/baseball/service/BaseballServiceTest.java index 5099f22cd3..76ac224c37 100644 --- a/src/test/java/baseball/service/BaseballServiceTest.java +++ b/src/test/java/baseball/service/BaseballServiceTest.java @@ -1,12 +1,17 @@ package baseball.service; +import baseball.domain.BaseballNumber; import baseball.domain.Game; +import baseball.domain.Match; +import baseball.domain.Matches; import baseball.util.NumberGenerator; import baseball.util.RandomNumberGenerator; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import java.util.List; + import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatThrownBy; @@ -24,5 +29,27 @@ void should_Return_Game() { assertThat(service.createGame()).isNotNull() .isInstanceOf(Game.class); } + + @DisplayName("게임 결과를 계산한다") + @Test + void should_Return_Matches() { + // given + BaseballNumber answer = BaseballNumber.from("123"); + Game game = new Game(answer); + NumberGenerator numberGenerator = new NumberGenerator() { + @Override + public List generateUniqueNumbersInRange(int size, int start, int end) { + return List.of(1,2,3); + } + }; + BaseballService service = new BaseballService(numberGenerator); + + // when + Matches matches = service.match(game, "123"); + + // then + assertThat(matches.getMatchCount(Match.STRIKE)).isEqualTo(3); + assertThat(matches.getMatchCount(Match.BALL)).isEqualTo(0); + } } } From b1895885fe54fde5d3d5a46c56788beb7370c28e Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Wed, 10 Dec 2025 23:12:22 +0900 Subject: [PATCH 7/9] =?UTF-8?q?feat:=20[=EA=B2=8C=EC=9E=84=20=EC=A7=84?= =?UTF-8?q?=ED=96=89]=20=EC=A0=95=EB=8B=B5=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EC=8B=9C=EB=8F=84=20=EA=B2=B0=EA=B3=BC=20=EC=B6=9C=EB=A0=A5=20?= =?UTF-8?q?=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 --- README.md | 3 +-- .../baseball/controller/GameController.java | 21 ++++++++++++++++- src/main/java/baseball/domain/Match.java | 4 ++-- .../exception/NoMatchResultException.java | 7 ++++++ .../baseball/service/BaseballService.java | 8 +++++++ .../java/baseball/view/ConsoleOutputView.java | 23 +++++++++++++++++++ src/main/java/baseball/view/OutputView.java | 6 +++++ 7 files changed, 67 insertions(+), 5 deletions(-) create mode 100644 src/main/java/baseball/exception/NoMatchResultException.java diff --git a/README.md b/README.md index 6999e0ed63..24d64f5aa9 100644 --- a/README.md +++ b/README.md @@ -35,8 +35,7 @@ - 같은 수가 존재하는 경우 - 같은 수가 같은 자리에 있는 경우 카운트 - 같은 수가 다른 자리에 있는 경우 카운트 - - 같은 수가 전혀 없는 경우: 낫싱 -- [ ] **정답 입력 시도 결과 출력** +- [x] **정답 입력 시도 결과 출력** - 일치 여부 결과 출력 - 같은 수가 존재하는 경우 (형식: `{일치 개수}{경우의 종류}`, 여러 개라면 한칸 띄우고 이어서 작성) - 같은 수가 같은 자리에 있는 경우: 스트라이크 diff --git a/src/main/java/baseball/controller/GameController.java b/src/main/java/baseball/controller/GameController.java index d74c6e8a46..2a81aefc6c 100644 --- a/src/main/java/baseball/controller/GameController.java +++ b/src/main/java/baseball/controller/GameController.java @@ -2,6 +2,7 @@ import baseball.domain.Game; import baseball.domain.Matches; +import baseball.exception.NoMatchResultException; import baseball.service.BaseballService; import baseball.view.InputView; import baseball.view.OutputView; @@ -22,10 +23,28 @@ public GameController(InputView inputView, OutputView outputView, BaseballServic public void run() { outputView.printGameStartInstruction(); + startGame(); + } + + private void startGame() { Game game = service.createGame(); + boolean flag = false; + do { + boolean isGameOver = guess(game); + flag = !isGameOver; + } while (flag); + } + private boolean guess(Game game) { outputView.printNumberInputPrompt(); String guessNumber = inputView.readGuessingNumber(); - Matches matchResult = service.match(game, guessNumber); + try { + Matches matchResult = service.match(game, guessNumber); + outputView.printMatchResults(matchResult); + return service.isGameOver(matchResult); + } catch (NoMatchResultException e) { + outputView.printMatchResults(); + } + return false; } } diff --git a/src/main/java/baseball/domain/Match.java b/src/main/java/baseball/domain/Match.java index 4acd2a2458..9e5597a2a8 100644 --- a/src/main/java/baseball/domain/Match.java +++ b/src/main/java/baseball/domain/Match.java @@ -3,8 +3,8 @@ import baseball.exception.NoMatchingNumberException; public enum Match { - STRIKE("스트라이크"), - BALL("볼"); + BALL("볼"), + STRIKE("스트라이크"); private final String message; diff --git a/src/main/java/baseball/exception/NoMatchResultException.java b/src/main/java/baseball/exception/NoMatchResultException.java new file mode 100644 index 0000000000..cd2cde9bb9 --- /dev/null +++ b/src/main/java/baseball/exception/NoMatchResultException.java @@ -0,0 +1,7 @@ +package baseball.exception; + +public class NoMatchResultException extends RuntimeException { + public NoMatchResultException() { + super(); + } +} diff --git a/src/main/java/baseball/service/BaseballService.java b/src/main/java/baseball/service/BaseballService.java index 35d1ee7fd5..1c47aee487 100644 --- a/src/main/java/baseball/service/BaseballService.java +++ b/src/main/java/baseball/service/BaseballService.java @@ -4,6 +4,7 @@ import baseball.domain.BaseballNumber; import baseball.domain.Match; import baseball.domain.Matches; +import baseball.exception.NoMatchResultException; import baseball.util.NumberGenerator; import java.util.List; @@ -31,6 +32,13 @@ public Game createGame() { public Matches match(Game game, String guessString) { BaseballNumber guessNumber = BaseballNumber.from(guessString); List matches = game.matchWithAnswer(guessNumber); + if (matches.isEmpty()) { + throw new NoMatchResultException(); + } return Matches.from(matches); } + + public boolean isGameOver(Matches matches) { + return matches.getMatchCount(Match.STRIKE) == NUMBER_SIZE; + } } diff --git a/src/main/java/baseball/view/ConsoleOutputView.java b/src/main/java/baseball/view/ConsoleOutputView.java index aa0a09320b..1faca957ca 100644 --- a/src/main/java/baseball/view/ConsoleOutputView.java +++ b/src/main/java/baseball/view/ConsoleOutputView.java @@ -1,5 +1,8 @@ package baseball.view; +import baseball.domain.Match; +import baseball.domain.Matches; + /** * 프로그램의 콘솔 출력을 담당하는 클래스 */ @@ -13,4 +16,24 @@ public void printGameStartInstruction() { public void printNumberInputPrompt() { System.out.print("숫자를 입력해주세요 : "); } + + @Override + public void printMatchResults() { + System.out.println("낫싱"); + } + + @Override + public void printMatchResults(Matches matches) { + for (Match match : Match.values()) { + int count = matches.getMatchCount(match); + if (count > 0) { + printMatchResult(match, count); + } + } + System.out.println(); + } + + private void printMatchResult(Match match, int count) { + System.out.printf("%d%s ", count, match.getMessage()); + } } diff --git a/src/main/java/baseball/view/OutputView.java b/src/main/java/baseball/view/OutputView.java index affd0f909c..e8a464321c 100644 --- a/src/main/java/baseball/view/OutputView.java +++ b/src/main/java/baseball/view/OutputView.java @@ -1,5 +1,7 @@ package baseball.view; +import baseball.domain.Matches; + /** * 프로그램의 모든 출력을 담당하는 인터페이스 */ @@ -7,4 +9,8 @@ public interface OutputView { default void printGameStartInstruction() {}; default void printNumberInputPrompt() {}; + + default void printMatchResults() {}; + + default void printMatchResults(Matches matchResult) {}; } \ No newline at end of file From a5c1f5825a726931a69d28cfe365db9023070398 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Wed, 10 Dec 2025 23:14:54 +0900 Subject: [PATCH 8/9] =?UTF-8?q?feat:=20[=EA=B2=8C=EC=9E=84=20=EC=A2=85?= =?UTF-8?q?=EB=A3=8C]=20=EA=B2=8C=EC=9E=84=20=EC=A2=85=EB=A3=8C=20?= =?UTF-8?q?=EC=95=88=EB=82=B4=20=EC=B6=9C=EB=A0=A5=20=EA=B8=B0=EB=8A=A5=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 --- README.md | 2 +- src/main/java/baseball/controller/GameController.java | 1 + src/main/java/baseball/view/ConsoleOutputView.java | 5 +++++ src/main/java/baseball/view/OutputView.java | 2 ++ 4 files changed, 9 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 24d64f5aa9..ca0fbec89d 100644 --- a/README.md +++ b/README.md @@ -45,7 +45,7 @@ - 정답을 맞춘 경우, 게임 종료 프로세스 진행 ### 3. 게임 종료 -- [ ] **게임 종료 안내 출력** (형식: `3개의 숫자를 모두 맞히셨습니다! 게임 종료`) +- [x] **게임 종료 안내 출력** (형식: `3개의 숫자를 모두 맞히셨습니다! 게임 종료`) - 3스트라이크로 3개의 숫자를 모두 맞춘 경우 게임 종료 - [ ] **새 게임 진행 여부 결정** - 게임 진행 안내 문구 출력 (형식: `게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.`) diff --git a/src/main/java/baseball/controller/GameController.java b/src/main/java/baseball/controller/GameController.java index 2a81aefc6c..1c029b0fa0 100644 --- a/src/main/java/baseball/controller/GameController.java +++ b/src/main/java/baseball/controller/GameController.java @@ -24,6 +24,7 @@ public GameController(InputView inputView, OutputView outputView, BaseballServic public void run() { outputView.printGameStartInstruction(); startGame(); + outputView.printGameEndInstruction(); } private void startGame() { diff --git a/src/main/java/baseball/view/ConsoleOutputView.java b/src/main/java/baseball/view/ConsoleOutputView.java index 1faca957ca..8f73de46bc 100644 --- a/src/main/java/baseball/view/ConsoleOutputView.java +++ b/src/main/java/baseball/view/ConsoleOutputView.java @@ -33,6 +33,11 @@ public void printMatchResults(Matches matches) { System.out.println(); } + @Override + public void printGameEndInstruction() { + System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 종료"); + }; + private void printMatchResult(Match match, int count) { System.out.printf("%d%s ", count, match.getMessage()); } diff --git a/src/main/java/baseball/view/OutputView.java b/src/main/java/baseball/view/OutputView.java index e8a464321c..658a322516 100644 --- a/src/main/java/baseball/view/OutputView.java +++ b/src/main/java/baseball/view/OutputView.java @@ -13,4 +13,6 @@ public interface OutputView { default void printMatchResults() {}; default void printMatchResults(Matches matchResult) {}; + + default void printGameEndInstruction() {}; } \ No newline at end of file From 7b713820ab69d5d5f9e1d9facf60817bcdba9710 Mon Sep 17 00:00:00 2001 From: Yeji Kim Date: Wed, 10 Dec 2025 23:38:21 +0900 Subject: [PATCH 9/9] =?UTF-8?q?feat:=20[=EA=B2=8C=EC=9E=84=20=EC=A2=85?= =?UTF-8?q?=EB=A3=8C]=20=EC=83=88=20=EA=B2=8C=EC=9E=84=20=EC=A7=84?= =?UTF-8?q?=ED=96=89=20=EC=97=AC=EB=B6=80=20=EA=B2=B0=EC=A0=95=20=EA=B8=B0?= =?UTF-8?q?=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 --- README.md | 2 +- .../baseball/controller/GameController.java | 14 ++++++++++++-- .../java/baseball/exception/ErrorMessage.java | 5 ++++- .../baseball/service/BaseballService.java | 19 ++++++++++++++++++- .../java/baseball/view/ConsoleInputView.java | 5 +++++ .../java/baseball/view/ConsoleOutputView.java | 5 +++++ src/main/java/baseball/view/InputView.java | 4 ++++ src/main/java/baseball/view/OutputView.java | 2 ++ 8 files changed, 51 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index ca0fbec89d..bc4f65d454 100644 --- a/README.md +++ b/README.md @@ -47,7 +47,7 @@ ### 3. 게임 종료 - [x] **게임 종료 안내 출력** (형식: `3개의 숫자를 모두 맞히셨습니다! 게임 종료`) - 3스트라이크로 3개의 숫자를 모두 맞춘 경우 게임 종료 -- [ ] **새 게임 진행 여부 결정** +- [x] **새 게임 진행 여부 결정** - 게임 진행 안내 문구 출력 (형식: `게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.`) - 사용자가 게임의 진행을 결정하는 숫자를 입력 - 1 또는 2를 입력 diff --git a/src/main/java/baseball/controller/GameController.java b/src/main/java/baseball/controller/GameController.java index 1c029b0fa0..8827797948 100644 --- a/src/main/java/baseball/controller/GameController.java +++ b/src/main/java/baseball/controller/GameController.java @@ -23,8 +23,12 @@ public GameController(InputView inputView, OutputView outputView, BaseballServic public void run() { outputView.printGameStartInstruction(); - startGame(); - outputView.printGameEndInstruction(); + boolean flag = false; + do { + startGame(); + outputView.printGameEndInstruction(); + flag = isStartingNewGame(); + } while (flag); } private void startGame() { @@ -48,4 +52,10 @@ private boolean guess(Game game) { } return false; } + + private boolean isStartingNewGame() { + outputView.printGameProceedPrompt(); + String gameProceed = inputView.readGameProceed(); + return service.isStartingNewGame(gameProceed); + } } diff --git a/src/main/java/baseball/exception/ErrorMessage.java b/src/main/java/baseball/exception/ErrorMessage.java index 064db1df05..b324204e13 100644 --- a/src/main/java/baseball/exception/ErrorMessage.java +++ b/src/main/java/baseball/exception/ErrorMessage.java @@ -9,7 +9,10 @@ public enum ErrorMessage { NUMBER_OUT_OF_RANGE("각 자리의 숫자는 1~9 사이의 정수여야 합니다."), NUMBER_NULL_OR_BLANK("숫자는 비워두거나 공백을 포함할 수 없습니다."), NUMBER_NOT_NUMERIC("숫자가 아닌 문자를 포함할 수 없습니다."), - NUMBER_DUPLICATED("세자리의 숫자는 서로 중복될 수 없습니다."); + NUMBER_DUPLICATED("세자리의 숫자는 서로 중복될 수 없습니다."), + + // BaseballService Error + INVALID_ANSWER("1 또는 2로 답변해야 합니다."); private static final String ERROR_PREFIX = "[ERROR] "; diff --git a/src/main/java/baseball/service/BaseballService.java b/src/main/java/baseball/service/BaseballService.java index 1c47aee487..caa6485815 100644 --- a/src/main/java/baseball/service/BaseballService.java +++ b/src/main/java/baseball/service/BaseballService.java @@ -4,7 +4,8 @@ import baseball.domain.BaseballNumber; import baseball.domain.Match; import baseball.domain.Matches; -import baseball.exception.NoMatchResultException; +import baseball.exception.*; +import baseball.util.InputParser; import baseball.util.NumberGenerator; import java.util.List; @@ -41,4 +42,20 @@ public Matches match(Game game, String guessString) { public boolean isGameOver(Matches matches) { return matches.getMatchCount(Match.STRIKE) == NUMBER_SIZE; } + + public boolean isStartingNewGame(String gameProceed) { + validateAnswer(gameProceed); + return gameProceed.equals("1"); + } + + private void validateAnswer(String gameProceed) { + try { + int answer = InputParser.parseToInt(gameProceed); + if (answer < 1 || answer > 2) { + throw new IllegalArgumentException(ErrorMessage.INVALID_ANSWER.getMessage()); + } + } catch (InputNullOrBlankException | InputNotNumericException | InputNumberOverflowException e) { + throw new IllegalArgumentException(ErrorMessage.INVALID_ANSWER.getMessage()); + } + } } diff --git a/src/main/java/baseball/view/ConsoleInputView.java b/src/main/java/baseball/view/ConsoleInputView.java index 41926d8412..ac5035ea14 100644 --- a/src/main/java/baseball/view/ConsoleInputView.java +++ b/src/main/java/baseball/view/ConsoleInputView.java @@ -10,4 +10,9 @@ public class ConsoleInputView implements InputView { public String readGuessingNumber() { return Console.readLine(); } + + @Override + public String readGameProceed() { + return Console.readLine(); + } } \ No newline at end of file diff --git a/src/main/java/baseball/view/ConsoleOutputView.java b/src/main/java/baseball/view/ConsoleOutputView.java index 8f73de46bc..7e12fe5db0 100644 --- a/src/main/java/baseball/view/ConsoleOutputView.java +++ b/src/main/java/baseball/view/ConsoleOutputView.java @@ -38,6 +38,11 @@ public void printGameEndInstruction() { System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 종료"); }; + @Override + public void printGameProceedPrompt() { + System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."); + } + private void printMatchResult(Match match, int count) { System.out.printf("%d%s ", count, match.getMessage()); } diff --git a/src/main/java/baseball/view/InputView.java b/src/main/java/baseball/view/InputView.java index ef863306ea..0e41b341e9 100644 --- a/src/main/java/baseball/view/InputView.java +++ b/src/main/java/baseball/view/InputView.java @@ -7,4 +7,8 @@ public interface InputView { default String readGuessingNumber() { return null; }; + + default String readGameProceed() { + return null; + }; } \ No newline at end of file diff --git a/src/main/java/baseball/view/OutputView.java b/src/main/java/baseball/view/OutputView.java index 658a322516..0ec53c200a 100644 --- a/src/main/java/baseball/view/OutputView.java +++ b/src/main/java/baseball/view/OutputView.java @@ -15,4 +15,6 @@ public interface OutputView { default void printMatchResults(Matches matchResult) {}; default void printGameEndInstruction() {}; + + default void printGameProceedPrompt() {}; } \ No newline at end of file