From c5c277d7b040873423bde9fed57d808b229cca43 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=A0=95=ED=99=98?= Date: Wed, 28 Jan 2026 16:25:56 +0900 Subject: [PATCH 01/18] =?UTF-8?q?docs:=20README.md=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 60 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 59 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d7e8aee..545c47e9 100644 --- a/README.md +++ b/README.md @@ -1 +1,59 @@ -# java-baseball-precourse \ No newline at end of file +# java-baseball-precourse + +### 기능 목록 +숫자야구 게임 기능을 구현해야 한다. + +1. 게임 시작 및 상대방(컴퓨터) 숫자 생성 + - 1에서 9까지의 서로 다른 임의의 수 3개를 생성하는 기능 + - 게임 시작 문구 출력 ("숫자 야구 게임을 시작합니다.") + - 컴퓨터의 랜덤 숫자 생성 로직 구현 + +2. 사용자 입력 및 예외 처리 + - 사용자로부터 3자리 숫자를 입력받는 기능 + - 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시키고 애플리케이션 종료 + - 3자리 숫자가 아닌 경우 + - 숫자가 아닌 문자가 포함된 경우 + - 중복된 숫자가 있는 경우 + - 0이 포함된 경우 (문제 조건에 따라 1~9 사이인지 확인) + - 사용자 입력 기능 및 유효성 검사 로직 구현 + +3. 게임 결과 판정 (힌트 생성) + - 입력한 숫자와 컴퓨터 숫자를 비교하여 결과 계산 + - 스트라이크: 같은 수가 같은 자리에 있는 경우 + - 볼: 같은 수가 다른 자리에 있는 경우 + - 낫싱: 같은 수가 전혀 없는 경우 + + - 판정 결과를 텍스트로 변환 (예: "1볼 1스트라이크", "낫싱") + - 스트라이크/볼 카운트 및 판정 결과 출력 로직 구현 + +4. 게임 종료 및 재시작/완전 종료 + - 3개 숫자를 모두 맞혔을 경우 안내 문구 출력 ("3스트라이크 \n 3개의 숫자를 모두 맞히셨습니다! 게임 종료") + - 게임 종료 후 재시작(1) 또는 종료(2)를 선택받는 기능 + - 1 입력 시: 새로운 숫자를 생성하고 게임 다시 시작 + - 2 입력 시: 애플리케이션 완전 종료 + - 게임 종료 조건 확인 및 재시작/종료 선택 로직 구현 + +--- +### 프로그래밍 요구사항1 - 제약사항 + +- 자바 코드 컨벤션을 지키면서 프로그래밍한다. + - https://naver.github.io/hackday-conventions-java/ +- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. + - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. + - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다. +- 자바 8에 추가된 stream api를 사용하지 않고 구현해야 한다. 단, 람다는 사용 가능하다. +- else 예약어를 쓰지 않는다. + - 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다. + - else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다. +- 함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다. + - 함수(또는 메소드)가 한 가지 일만 잘 하도록 구현한다 + +--- +### 프로그래밍 요구사항2 - 단위 테스트 + +- 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(System.out, System.in, Scanner) 로직은 제외 + - 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리해 구현한다. + - 힌트는 MVC 패턴 기반으로 구현한 후 View, Controller를 제외한 Model에 대한 단위 테스트를 추가하는 것에 집 + 중한다. +- JUnit5와 AssertJ 사용법에 익숙하지 않은 개발자는 첨부한 "학습테스트를 통해 JUnit 학습하기.pdf" 문서를 참고해 + 사용법을 학습한 후 JUnit5 기반 단위 테스트를 구현한다. \ No newline at end of file From b5c83678b2cd0262b4c2a9c32a2a56a2082effc1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=A0=95=ED=99=98?= Date: Wed, 28 Jan 2026 16:29:47 +0900 Subject: [PATCH 02/18] docs: update README.md --- README.md | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/README.md b/README.md index 545c47e9..8d3c8ca0 100644 --- a/README.md +++ b/README.md @@ -1,14 +1,15 @@ # java-baseball-precourse -### 기능 목록 +## 기능 목록 + 숫자야구 게임 기능을 구현해야 한다. -1. 게임 시작 및 상대방(컴퓨터) 숫자 생성 +### 게임 시작 및 상대방(컴퓨터) 숫자 생성 - 1에서 9까지의 서로 다른 임의의 수 3개를 생성하는 기능 - 게임 시작 문구 출력 ("숫자 야구 게임을 시작합니다.") - 컴퓨터의 랜덤 숫자 생성 로직 구현 -2. 사용자 입력 및 예외 처리 +### 사용자 입력 및 예외 처리 - 사용자로부터 3자리 숫자를 입력받는 기능 - 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시키고 애플리케이션 종료 - 3자리 숫자가 아닌 경우 @@ -17,7 +18,7 @@ - 0이 포함된 경우 (문제 조건에 따라 1~9 사이인지 확인) - 사용자 입력 기능 및 유효성 검사 로직 구현 -3. 게임 결과 판정 (힌트 생성) +### 게임 결과 판정 (힌트 생성) - 입력한 숫자와 컴퓨터 숫자를 비교하여 결과 계산 - 스트라이크: 같은 수가 같은 자리에 있는 경우 - 볼: 같은 수가 다른 자리에 있는 경우 @@ -26,7 +27,7 @@ - 판정 결과를 텍스트로 변환 (예: "1볼 1스트라이크", "낫싱") - 스트라이크/볼 카운트 및 판정 결과 출력 로직 구현 -4. 게임 종료 및 재시작/완전 종료 +### 게임 종료 및 재시작/완전 종료 - 3개 숫자를 모두 맞혔을 경우 안내 문구 출력 ("3스트라이크 \n 3개의 숫자를 모두 맞히셨습니다! 게임 종료") - 게임 종료 후 재시작(1) 또는 종료(2)를 선택받는 기능 - 1 입력 시: 새로운 숫자를 생성하고 게임 다시 시작 @@ -34,7 +35,7 @@ - 게임 종료 조건 확인 및 재시작/종료 선택 로직 구현 --- -### 프로그래밍 요구사항1 - 제약사항 +## 프로그래밍 요구사항1 - 제약사항 - 자바 코드 컨벤션을 지키면서 프로그래밍한다. - https://naver.github.io/hackday-conventions-java/ @@ -49,7 +50,7 @@ - 함수(또는 메소드)가 한 가지 일만 잘 하도록 구현한다 --- -### 프로그래밍 요구사항2 - 단위 테스트 +## 프로그래밍 요구사항2 - 단위 테스트 - 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(System.out, System.in, Scanner) 로직은 제외 - 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리해 구현한다. From fab2c9fe2fc923e8d8cdb574d21a14def56c494e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=A0=95=ED=99=98?= Date: Wed, 28 Jan 2026 16:50:57 +0900 Subject: [PATCH 03/18] =?UTF-8?q?feat:=20=EA=B2=8C=EC=9E=84=EC=8B=9C?= =?UTF-8?q?=EC=9E=91=20=EB=AC=B8=EA=B5=AC=20=EC=B6=9C=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/BaseballGameClient.java | 10 ++++++++++ src/main/java/controller/BaseballGameController.java | 11 +++++++++++ 2 files changed, 21 insertions(+) create mode 100644 src/main/java/BaseballGameClient.java create mode 100644 src/main/java/controller/BaseballGameController.java diff --git a/src/main/java/BaseballGameClient.java b/src/main/java/BaseballGameClient.java new file mode 100644 index 00000000..f2d324a4 --- /dev/null +++ b/src/main/java/BaseballGameClient.java @@ -0,0 +1,10 @@ +import controller.BaseballGameController; + +public class BaseballGameClient { + + public static void main(String[] args) { + BaseballGameController gameController = new BaseballGameController(); + gameController.start(); + } +} + diff --git a/src/main/java/controller/BaseballGameController.java b/src/main/java/controller/BaseballGameController.java new file mode 100644 index 00000000..359162cf --- /dev/null +++ b/src/main/java/controller/BaseballGameController.java @@ -0,0 +1,11 @@ +package controller; + +public class BaseballGameController { + public BaseballGameController() { + } + + public void start() { + System.out.println("숫자 야구 게임을 시작합니다."); + + } +} From 7cbcd194879e279c2a2180cee18d74247887c37b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=A0=95=ED=99=98?= Date: Wed, 28 Jan 2026 17:23:26 +0900 Subject: [PATCH 04/18] =?UTF-8?q?feat:=20=EC=BB=B4=ED=93=A8=ED=84=B0=20?= =?UTF-8?q?=EC=88=AB=EC=9E=90=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 1에서 9까지의 서로 다른 임의의 수 3개를 생성 - 컴퓨터의 랜덤 숫자 생성 로직 구현 --- .../controller/BaseballGameController.java | 16 +++++++++ .../java/utils/RandomNumberGenerator.java | 34 +++++++++++++++++++ 2 files changed, 50 insertions(+) create mode 100644 src/main/java/utils/RandomNumberGenerator.java diff --git a/src/main/java/controller/BaseballGameController.java b/src/main/java/controller/BaseballGameController.java index 359162cf..76d87010 100644 --- a/src/main/java/controller/BaseballGameController.java +++ b/src/main/java/controller/BaseballGameController.java @@ -1,11 +1,27 @@ package controller; +import utils.RandomNumberGenerator; + public class BaseballGameController { + public BaseballGameController() { + } public void start() { System.out.println("숫자 야구 게임을 시작합니다."); + do { + playGame(); + } while (checkRestart()); + } + + private void playGame() { + int[] numbers = RandomNumberGenerator.generate(); + + } + + private boolean checkRestart() { + return false; } } diff --git a/src/main/java/utils/RandomNumberGenerator.java b/src/main/java/utils/RandomNumberGenerator.java new file mode 100644 index 00000000..0aff39b2 --- /dev/null +++ b/src/main/java/utils/RandomNumberGenerator.java @@ -0,0 +1,34 @@ +package utils; + +import java.util.Random; + +public class RandomNumberGenerator { + private static final Random random = new Random(); + + public static int[] generate() { + int[] numbers = new int[3]; + int index = 0; + + while (index < numbers.length) { + int num = getNumber(); + if (isDuplicate(numbers, num)){ + continue; + } + numbers[index] = num; + index++; + } + + return numbers; + } + + private static int getNumber(){ + return random.nextInt(1, 10); + } + + private static boolean isDuplicate(int[] numbers, int num){ + for (int i = 0; i < 3; i++) { + if (numbers[i] == num) return true; + } + return false; + } +} From 432d577972970019d5d1900805d7d7042b0d738d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=A0=95=ED=99=98?= Date: Wed, 28 Jan 2026 18:04:17 +0900 Subject: [PATCH 05/18] =?UTF-8?q?fix:=20=EB=9E=9C=EB=8D=A4=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=EB=B2=88=ED=98=B8=20=ED=83=80=EC=9E=85=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/utils/RandomNumberGenerator.java | 19 ++++++++++++------- 1 file changed, 12 insertions(+), 7 deletions(-) diff --git a/src/main/java/utils/RandomNumberGenerator.java b/src/main/java/utils/RandomNumberGenerator.java index 0aff39b2..30116aac 100644 --- a/src/main/java/utils/RandomNumberGenerator.java +++ b/src/main/java/utils/RandomNumberGenerator.java @@ -1,20 +1,23 @@ package utils; +import java.util.ArrayList; +import java.util.List; import java.util.Random; public class RandomNumberGenerator { private static final Random random = new Random(); - public static int[] generate() { - int[] numbers = new int[3]; + public static List generate() { + List numbers = new ArrayList<>(); int index = 0; - while (index < numbers.length) { + while (index < 3) { int num = getNumber(); if (isDuplicate(numbers, num)){ continue; } - numbers[index] = num; + + numbers.add(num); index++; } @@ -25,10 +28,12 @@ private static int getNumber(){ return random.nextInt(1, 10); } - private static boolean isDuplicate(int[] numbers, int num){ - for (int i = 0; i < 3; i++) { - if (numbers[i] == num) return true; + private static boolean isDuplicate(List numbers, int num){ + for (Integer number : numbers) { + if (number == num) + return true; } + return false; } } From 7fa31c1e0e8b5f57223f4aa6d51043721be61dc0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=A0=95=ED=99=98?= Date: Wed, 28 Jan 2026 18:20:50 +0900 Subject: [PATCH 06/18] =?UTF-8?q?docs:=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EB=B6=80=EB=B6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 애플리케이션 종료 -> [ERROR]로 시작하는 에러 메시지를 출력 --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d3c8ca0..2ff45774 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ### 사용자 입력 및 예외 처리 - 사용자로부터 3자리 숫자를 입력받는 기능 - - 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시키고 애플리케이션 종료 + - 잘못된 값을 입력할 경우 [ERROR]로 시작하는 에러 메시지를 출력 - 3자리 숫자가 아닌 경우 - 숫자가 아닌 문자가 포함된 경우 - 중복된 숫자가 있는 경우 From f0d2d8b4fb834a38764cc88a5e3ff532f46ddc17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=A0=95=ED=99=98?= Date: Wed, 28 Jan 2026 18:37:42 +0900 Subject: [PATCH 07/18] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=EB=B0=8F=20=EC=98=88=EC=99=B8=20=EC=B2=98?= =?UTF-8?q?=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 사용자로부터 3자리 숫자를 입력받는 기능 - 잘못된 값을 입력할 경우 [ERROR]로 시작 - 사용자 입력 기능 및 유효성 검사 로직 구현 --- .../controller/BaseballGameController.java | 13 ++--- src/main/java/domain/BaseballNumbers.java | 55 +++++++++++++++++++ src/main/java/service/BaseballService.java | 32 +++++++++++ src/main/java/view/InputView.java | 11 ++++ src/main/java/view/OutputView.java | 14 +++++ 5 files changed, 116 insertions(+), 9 deletions(-) create mode 100644 src/main/java/domain/BaseballNumbers.java create mode 100644 src/main/java/service/BaseballService.java create mode 100644 src/main/java/view/InputView.java create mode 100644 src/main/java/view/OutputView.java diff --git a/src/main/java/controller/BaseballGameController.java b/src/main/java/controller/BaseballGameController.java index 76d87010..f773c046 100644 --- a/src/main/java/controller/BaseballGameController.java +++ b/src/main/java/controller/BaseballGameController.java @@ -1,25 +1,20 @@ package controller; -import utils.RandomNumberGenerator; +import service.BaseballService; public class BaseballGameController { + private final BaseballService baseballService; public BaseballGameController() { - + baseballService = new BaseballService(); } public void start() { - System.out.println("숫자 야구 게임을 시작합니다."); do { - playGame(); + baseballService.playGame(); } while (checkRestart()); } - private void playGame() { - int[] numbers = RandomNumberGenerator.generate(); - - } - private boolean checkRestart() { return false; diff --git a/src/main/java/domain/BaseballNumbers.java b/src/main/java/domain/BaseballNumbers.java new file mode 100644 index 00000000..d6c4fea5 --- /dev/null +++ b/src/main/java/domain/BaseballNumbers.java @@ -0,0 +1,55 @@ +package domain; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; + +public class BaseballNumbers { + private final List numbers; + + public BaseballNumbers(String numbers) { + validateLength(numbers); + this.numbers = parseInput(numbers); + } + + public BaseballNumbers(List numbers) { + this.numbers = numbers; + } + + private List parseInput(String input) { + List numbers = new ArrayList<>(); + + for (char c : input.toCharArray()) { + int number = Character.getNumericValue(c); + validateNumeric(number); + validateNumberRange(number); + + numbers.add(number); + } + + validateDuplicate(numbers); + return numbers; + } + + private void validateDuplicate(List numbers) { + HashSet compare = new HashSet<>(numbers); + + if (numbers.size() != compare.size()) { + throw new IllegalArgumentException("중복된 숫자가 있습니다."); + } + } + + private void validateLength(String numbers) { + if (numbers.length() != 3) throw new IllegalArgumentException("입력은 세 글자만 가능합니다."); + } + + private void validateNumeric(int numbers) { + if (numbers == -1) throw new IllegalArgumentException("숫자가 아닌 문자가 포함되었습니다."); + } + + private void validateNumberRange(int number) { + if (number < 1 || number > 9) { + throw new IllegalArgumentException("1에서 9 사이의 숫자만 입력 가능합니다."); + } + } +} diff --git a/src/main/java/service/BaseballService.java b/src/main/java/service/BaseballService.java new file mode 100644 index 00000000..a662233d --- /dev/null +++ b/src/main/java/service/BaseballService.java @@ -0,0 +1,32 @@ +package service; + +import domain.BaseballNumbers; +import utils.RandomNumberGenerator; +import view.InputView; +import view.OutputView; + +public class BaseballService { + + private final InputView inputView; + private final OutputView outputView; + + public BaseballService() { + inputView = new InputView(); + outputView = new OutputView(); + } + + public void playGame() { + outputView.printStartMessage(); + + BaseballNumbers computer = new BaseballNumbers(RandomNumberGenerator.generate()); + boolean isMatch = false; + + while (!isMatch) { + try { + BaseballNumbers player = new BaseballNumbers(inputView.readInput()); + } catch (IllegalArgumentException e) { + outputView.printErrorMessage(e.getMessage()); + } + } + } +} diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java new file mode 100644 index 00000000..69178eda --- /dev/null +++ b/src/main/java/view/InputView.java @@ -0,0 +1,11 @@ +package view; + +import java.util.Scanner; + +public class InputView { + Scanner sc = new Scanner(System.in); + + public String readInput() { + return sc.nextLine(); + } +} diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java new file mode 100644 index 00000000..6064c0f5 --- /dev/null +++ b/src/main/java/view/OutputView.java @@ -0,0 +1,14 @@ +package view; + +public class OutputView { + private static final String ERROR_PREFIX = "[ERROR] "; + + public void printStartMessage() { + System.out.println("숫자 야구 게임을 시작합니다."); + + } + + public void printErrorMessage(String message) { + System.out.println(ERROR_PREFIX + message); + } +} From ce573250bd826629112dbf8c3876661d680f8da2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=A0=95=ED=99=98?= Date: Sun, 1 Feb 2026 14:19:18 +0900 Subject: [PATCH 08/18] =?UTF-8?q?feat:=20=EC=8A=A4=ED=8A=B8=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=ED=81=AC=20=EB=B0=8F=20=EB=B3=BC=20=EC=B9=B4=EC=9A=B4?= =?UTF-8?q?=ED=8A=B8=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/BaseballNumbers.java | 34 +++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/main/java/domain/BaseballNumbers.java b/src/main/java/domain/BaseballNumbers.java index d6c4fea5..387f4871 100644 --- a/src/main/java/domain/BaseballNumbers.java +++ b/src/main/java/domain/BaseballNumbers.java @@ -3,6 +3,7 @@ import java.util.ArrayList; import java.util.HashSet; import java.util.List; +import java.util.Set; public class BaseballNumbers { private final List numbers; @@ -52,4 +53,37 @@ private void validateNumberRange(int number) { throw new IllegalArgumentException("1에서 9 사이의 숫자만 입력 가능합니다."); } } + + public int countStrike(BaseballNumbers other) { + int strike = 0; + for (int i = 0; i < numbers.size(); i++) { + strike += checkStrikeAt(other, i); + } + return strike; + } + + private int checkStrikeAt(BaseballNumbers other, int index) { + if (this.numbers.get(index).equals(other.numbers.get(index))) { + return 1; + } + return 0; + } + + public int countBall(BaseballNumbers other) { + int totalMatching = 0; + Set computerSet = new HashSet<>(this.numbers); + + for (int number : other.numbers) { + totalMatching += checkMatching(computerSet, number); + } + + return totalMatching - countStrike(other); + } + + private int checkMatching(Set set, int number) { + if (set.contains(number)) { + return 1; + } + return 0; + } } From 63dec2607031a085144dc387b313b3fced5087dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=A0=95=ED=99=98?= Date: Sun, 1 Feb 2026 14:27:54 +0900 Subject: [PATCH 09/18] =?UTF-8?q?refactor:=20=EC=BB=A8=ED=8A=B8=EB=A1=A4?= =?UTF-8?q?=EB=9F=AC=EC=99=80=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EC=B1=85?= =?UTF-8?q?=EC=9E=84=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/BaseballGameController.java | 34 ++++++++++++---- src/main/java/service/BaseballService.java | 39 +++++++++---------- 2 files changed, 45 insertions(+), 28 deletions(-) diff --git a/src/main/java/controller/BaseballGameController.java b/src/main/java/controller/BaseballGameController.java index f773c046..e82b1945 100644 --- a/src/main/java/controller/BaseballGameController.java +++ b/src/main/java/controller/BaseballGameController.java @@ -1,22 +1,40 @@ package controller; +import domain.BaseballNumbers; import service.BaseballService; +import utils.RandomNumberGenerator; +import view.InputView; +import view.OutputView; public class BaseballGameController { - private final BaseballService baseballService; + private final BaseballService baseballService = new BaseballService(); + private final InputView inputView = new InputView(); + private final OutputView outputView = new OutputView(); public BaseballGameController() { - baseballService = new BaseballService(); } public void start() { - do { - baseballService.playGame(); - } while (checkRestart()); - } + outputView.printStartMessage(); + BaseballNumbers computer = new BaseballNumbers(RandomNumberGenerator.generate()); + + while (true) { + BaseballNumbers player = askPlayerNumbers(); + String result = baseballService.playRound(computer, player); + outputView.printResult(result); - private boolean checkRestart() { + if (result.equals("3스트라이크")) break; + } + outputView.printGameEnd(); + } - return false; + private BaseballNumbers askPlayerNumbers() { + while (true) { + try { + return new BaseballNumbers(inputView.readInput()); + } catch (IllegalArgumentException e) { + outputView.printErrorMessage(e.getMessage()); + } + } } } diff --git a/src/main/java/service/BaseballService.java b/src/main/java/service/BaseballService.java index a662233d..897ebd8b 100644 --- a/src/main/java/service/BaseballService.java +++ b/src/main/java/service/BaseballService.java @@ -1,32 +1,31 @@ package service; import domain.BaseballNumbers; -import utils.RandomNumberGenerator; -import view.InputView; -import view.OutputView; -public class BaseballService { - private final InputView inputView; - private final OutputView outputView; +public class BaseballService { - public BaseballService() { - inputView = new InputView(); - outputView = new OutputView(); + public String playRound(BaseballNumbers computer, BaseballNumbers player) { + int strike = computer.countStrike(player); + int ball = computer.countBall(player); + return getResultMessage(strike, ball); } - public void playGame() { - outputView.printStartMessage(); - - BaseballNumbers computer = new BaseballNumbers(RandomNumberGenerator.generate()); - boolean isMatch = false; + public String getResultMessage(int strike, int ball) { + if (strike == 0 && ball == 0) { + return "낫싱"; + } + return buildStrikeBallMessage(strike, ball).trim(); + } - while (!isMatch) { - try { - BaseballNumbers player = new BaseballNumbers(inputView.readInput()); - } catch (IllegalArgumentException e) { - outputView.printErrorMessage(e.getMessage()); - } + private String buildStrikeBallMessage(int strike, int ball) { + String message = ""; + if (ball > 0) { + message += ball + "볼 "; + } + if (strike > 0) { + message += strike + "스트라이크"; } + return message; } } From 6ba2282778ae32778a9e8fa55d518c3c1d2d55fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=A0=95=ED=99=98?= Date: Sun, 1 Feb 2026 14:29:21 +0900 Subject: [PATCH 10/18] =?UTF-8?q?feat:=20=ED=8C=90=EC=A1=8D=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=EC=97=90=20=EB=94=B0=EB=A5=B8=20=ED=85=8D=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=9C=EB=A0=A5=20=EB=A1=9C=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 판정 결과를 텍스트로 변환 (예: "1볼 1스트라이크", "낫싱") - 3개 숫자를 모두 맞혔을 경우 안내 문구 출력 --- src/main/java/view/OutputView.java | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 6064c0f5..99775356 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -2,10 +2,21 @@ public class OutputView { private static final String ERROR_PREFIX = "[ERROR] "; + private static final String GAME_START_MESSAGE = "숫자 야구 게임을 시작합니다."; + private static final String GAME_END_MESSAGE = "3스트라이크 \\n 3개의 숫자를 모두 맞히셨습니다! 게임 종료"; + private static final String RESTART_GUIDE_MESSAGE = "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."; public void printStartMessage() { - System.out.println("숫자 야구 게임을 시작합니다."); + System.out.println(GAME_START_MESSAGE); + } + + public void printResult(String resultMessage) { + System.out.println(resultMessage); + } + public void printGameEnd() { + System.out.println(GAME_END_MESSAGE); + System.out.println(RESTART_GUIDE_MESSAGE); } public void printErrorMessage(String message) { From 7ea31869377073f4669d41a5e444ff4a136f420b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=A0=95=ED=99=98?= Date: Sun, 1 Feb 2026 14:29:58 +0900 Subject: [PATCH 11/18] =?UTF-8?q?refactor:=20=EB=B9=88=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=EC=9E=90=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/controller/BaseballGameController.java | 3 --- 1 file changed, 3 deletions(-) diff --git a/src/main/java/controller/BaseballGameController.java b/src/main/java/controller/BaseballGameController.java index e82b1945..67c3ce67 100644 --- a/src/main/java/controller/BaseballGameController.java +++ b/src/main/java/controller/BaseballGameController.java @@ -11,9 +11,6 @@ public class BaseballGameController { private final InputView inputView = new InputView(); private final OutputView outputView = new OutputView(); - public BaseballGameController() { - } - public void start() { outputView.printStartMessage(); BaseballNumbers computer = new BaseballNumbers(RandomNumberGenerator.generate()); From 28e92e8d348bb38ffff0839f3e75d9916ea9a06b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=A0=95=ED=99=98?= Date: Sun, 1 Feb 2026 15:30:10 +0900 Subject: [PATCH 12/18] =?UTF-8?q?refactor:=20=EB=A9=94=EB=AA=A8=EB=A6=AC?= =?UTF-8?q?=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - String -> StringBuilder --- src/main/java/service/BaseballService.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/main/java/service/BaseballService.java b/src/main/java/service/BaseballService.java index 897ebd8b..9f8a7845 100644 --- a/src/main/java/service/BaseballService.java +++ b/src/main/java/service/BaseballService.java @@ -19,13 +19,13 @@ public String getResultMessage(int strike, int ball) { } private String buildStrikeBallMessage(int strike, int ball) { - String message = ""; + StringBuilder message = new StringBuilder(); if (ball > 0) { - message += ball + "볼 "; + message.append(ball).append("볼 "); } if (strike > 0) { - message += strike + "스트라이크"; + message.append(strike).append("스트라이크"); } - return message; + return message.toString(); } } From ce6dbcc45e2d86e3b37e838788c6d30979e0985f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=A0=95=ED=99=98?= Date: Sun, 1 Feb 2026 15:34:33 +0900 Subject: [PATCH 13/18] =?UTF-8?q?feat:=20=EC=9E=AC=EC=8B=9C=EB=8F=84=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/BaseballGameController.java | 36 +++++++++++++++---- src/main/java/view/InputView.java | 12 +++++++ 2 files changed, 41 insertions(+), 7 deletions(-) diff --git a/src/main/java/controller/BaseballGameController.java b/src/main/java/controller/BaseballGameController.java index 67c3ce67..a948206e 100644 --- a/src/main/java/controller/BaseballGameController.java +++ b/src/main/java/controller/BaseballGameController.java @@ -12,19 +12,30 @@ public class BaseballGameController { private final OutputView outputView = new OutputView(); public void start() { + boolean isRunning = true; + while (isRunning) { + playOneGame(); + isRunning = askRestart(); + } + } + + private void playOneGame() { outputView.printStartMessage(); BaseballNumbers computer = new BaseballNumbers(RandomNumberGenerator.generate()); - - while (true) { - BaseballNumbers player = askPlayerNumbers(); - String result = baseballService.playRound(computer, player); - outputView.printResult(result); - - if (result.equals("3스트라이크")) break; + boolean isGameEnd = false; + while (!isGameEnd) { + isGameEnd = playTurn(computer); } outputView.printGameEnd(); } + private boolean playTurn(BaseballNumbers computer) { + BaseballNumbers player = askPlayerNumbers(); + String result = baseballService.playRound(computer, player); + outputView.printResult(result); + return result.equals("3스트라이크"); + } + private BaseballNumbers askPlayerNumbers() { while (true) { try { @@ -34,4 +45,15 @@ private BaseballNumbers askPlayerNumbers() { } } } + + private boolean askRestart() { + while (true) { + try { + String input = inputView.readRestartInput(); + return input.equals("1"); + } catch (IllegalArgumentException e) { + outputView.printErrorMessage(e.getMessage()); + } + } + } } diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index 69178eda..c969227b 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -8,4 +8,16 @@ public class InputView { public String readInput() { return sc.nextLine(); } + + public String readRestartInput() { + String input = readInput(); // 공통 입력 메서드 호출 + validateRestart(input); + return input; + } + + private void validateRestart(String input) { + if (!input.equals("1") && !input.equals("2")) { + throw new IllegalArgumentException("1 또는 2만 입력 가능합니다."); + } + } } From c8ce266fae8adca3b113b2f57e59b4c48e8fcc25 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=A0=95=ED=99=98?= Date: Sun, 1 Feb 2026 15:34:41 +0900 Subject: [PATCH 14/18] =?UTF-8?q?feat:=20=EC=9E=AC=EC=8B=9C=EB=8F=84=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/view/OutputView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/view/OutputView.java b/src/main/java/view/OutputView.java index 99775356..cb7a1bff 100644 --- a/src/main/java/view/OutputView.java +++ b/src/main/java/view/OutputView.java @@ -3,7 +3,7 @@ public class OutputView { private static final String ERROR_PREFIX = "[ERROR] "; private static final String GAME_START_MESSAGE = "숫자 야구 게임을 시작합니다."; - private static final String GAME_END_MESSAGE = "3스트라이크 \\n 3개의 숫자를 모두 맞히셨습니다! 게임 종료"; + private static final String GAME_END_MESSAGE = "3개의 숫자를 모두 맞히셨습니다! 게임 종료"; private static final String RESTART_GUIDE_MESSAGE = "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."; public void printStartMessage() { From da0f68973b852630c6d9df46cf14abe04c31d684 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=A0=95=ED=99=98?= Date: Sun, 1 Feb 2026 15:44:16 +0900 Subject: [PATCH 15/18] =?UTF-8?q?fix:=20=EC=98=81=EB=AC=B8=EC=9E=90=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=20=EC=98=88=EC=99=B8=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/domain/BaseballNumbers.java | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/main/java/domain/BaseballNumbers.java b/src/main/java/domain/BaseballNumbers.java index 387f4871..754c891b 100644 --- a/src/main/java/domain/BaseballNumbers.java +++ b/src/main/java/domain/BaseballNumbers.java @@ -21,8 +21,8 @@ private List parseInput(String input) { List numbers = new ArrayList<>(); for (char c : input.toCharArray()) { + validateNumeric(c); int number = Character.getNumericValue(c); - validateNumeric(number); validateNumberRange(number); numbers.add(number); @@ -44,8 +44,10 @@ private void validateLength(String numbers) { if (numbers.length() != 3) throw new IllegalArgumentException("입력은 세 글자만 가능합니다."); } - private void validateNumeric(int numbers) { - if (numbers == -1) throw new IllegalArgumentException("숫자가 아닌 문자가 포함되었습니다."); + private void validateNumeric(char c) { + if (!Character.isDigit(c)) { + throw new IllegalArgumentException("숫자 이외의 문자는 입력할 수 없습니다."); + } } private void validateNumberRange(int number) { From 7ccdecda0cf05cfca546dc2b737e15d0d837223c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=A0=95=ED=99=98?= Date: Sun, 1 Feb 2026 16:17:56 +0900 Subject: [PATCH 16/18] =?UTF-8?q?feat:=20=EC=9E=85=EB=A0=A5=EA=B0=92=20?= =?UTF-8?q?=EC=9C=A0=ED=9A=A8=20=EA=B2=80=EC=A6=9D=20=EB=B0=8F=20=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=EB=9D=BC=EC=9D=B4=ED=81=AC,=20=EB=B3=BC=20=EC=B9=B4?= =?UTF-8?q?=EC=9A=B4=ED=8A=B8=20=EA=B2=80=EC=A6=9D=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/domain/BaseballNumbers.java | 4 + src/test/java/domain/BaseballNumbersTest.java | 87 +++++++++++++++++++ 2 files changed, 91 insertions(+) create mode 100644 src/test/java/domain/BaseballNumbersTest.java diff --git a/src/main/java/domain/BaseballNumbers.java b/src/main/java/domain/BaseballNumbers.java index 754c891b..1e906449 100644 --- a/src/main/java/domain/BaseballNumbers.java +++ b/src/main/java/domain/BaseballNumbers.java @@ -88,4 +88,8 @@ private int checkMatching(Set set, int number) { } return 0; } + + public List getNumbers() { + return numbers; + } } diff --git a/src/test/java/domain/BaseballNumbersTest.java b/src/test/java/domain/BaseballNumbersTest.java new file mode 100644 index 00000000..3cd2bd06 --- /dev/null +++ b/src/test/java/domain/BaseballNumbersTest.java @@ -0,0 +1,87 @@ +package domain; + +import static org.assertj.core.api.Assertions.*; + +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; +import org.junit.jupiter.params.provider.ValueSource; + +public class BaseballNumbersTest { + + @ParameterizedTest + @ValueSource(strings = {"12", "1234", ""}) + @DisplayName("입력된 숫자가 3자리가 아니면 IllegalArgumentException이 발생한다") + void validateLengthTest(String input) { + assertThatThrownBy(() -> new BaseballNumbers(input)) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + @DisplayName("정상적인 숫자 입력 시 List로 변환되어 저장된다") + void createNumbersTest() { + BaseballNumbers numbers = new BaseballNumbers("123"); + assertThat(numbers.getNumbers()).containsExactly(1, 2, 3); + } + + @ParameterizedTest + @ValueSource(strings = {"112", "122", "333", "919"}) + @DisplayName("중복된 숫자가 입력되면 IllegalArgumentException이 발생한다") + void validateDuplicateTest(String input) { + assertThatThrownBy(() -> new BaseballNumbers(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("중복된 숫자가 있습니다."); + } + + @ParameterizedTest + @ValueSource(strings = {"aaa", "AAA", "춘식이", "라2언"}) + @DisplayName("문자 입력시 IllegalArgumentException이 발생한다") + void validateNumericTest(String input) { + assertThatThrownBy(() -> new BaseballNumbers(input)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("숫자 이외의 문자는 입력할 수 없습니다."); + } + + @Test + @DisplayName("1미만, 9초과시 IllegalArgumentException이 발생한다") + void validateNumberRangeTest() { + assertThatThrownBy(() -> new BaseballNumbers("000")) + .isInstanceOf(IllegalArgumentException.class) + .hasMessageContaining("1에서 9 사이의 숫자만 입력 가능합니다."); + } + + @ParameterizedTest + @CsvSource(value = { + "123:3", // 3스트라이크 + "124:2", // 2스트라이크 + "145:1", // 1스트라이크 + "456:0" // 0스트라이크 + }, delimiter = ':') + @DisplayName("다양한 입력값에 대해 스트라이크 개수를 검증한다") + void countStrikeTest(String input, int expectedStrike) { + BaseballNumbers computer = new BaseballNumbers("123"); + BaseballNumbers player = new BaseballNumbers(input); + + int actualStrike = computer.countStrike(player); + + assertThat(actualStrike).isEqualTo(expectedStrike); + } + + @ParameterizedTest + @CsvSource(value = { + "312:3", // 3볼 + "132:2", // 2볼 + "761:1", // 1볼 + "456:0" // 낫싱 + }, delimiter = ':') + @DisplayName("다양한 입력값에 대해 볼 개수를 검증한다") + void countBallTest(String input, int expectedBall) { + BaseballNumbers computer = new BaseballNumbers("123"); + BaseballNumbers player = new BaseballNumbers(input); + + int actualStrike = computer.countBall(player); + + assertThat(actualStrike).isEqualTo(expectedBall); + } +} From 3ee8c640c22219d352b8f497522071ffdc5238ce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=A0=95=ED=99=98?= Date: Sun, 1 Feb 2026 16:30:22 +0900 Subject: [PATCH 17/18] =?UTF-8?q?feat:=20=EC=8A=A4=ED=8A=B8=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=ED=81=AC,=20=EB=B3=BC=20=EA=B0=9C=EC=88=98=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EC=83=9D=EC=84=B1=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/service/BaseballServiceTest.java | 28 +++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/test/java/service/BaseballServiceTest.java diff --git a/src/test/java/service/BaseballServiceTest.java b/src/test/java/service/BaseballServiceTest.java new file mode 100644 index 00000000..ea9df70c --- /dev/null +++ b/src/test/java/service/BaseballServiceTest.java @@ -0,0 +1,28 @@ +package service; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.CsvSource; + +public class BaseballServiceTest { + + private final BaseballService baseballService = new BaseballService(); + + @ParameterizedTest + @CsvSource(value = { + "0, 0, 낫싱", + "0, 1, 1볼", + "0, 3, 3볼", + "1, 0, 1스트라이크", + "3, 0, 3스트라이크", + "1, 1, 1볼 1스트라이크", + "2, 1, 1볼 2스트라이크" // 상황에 따라 순서 확인 필요 (보통 볼이 먼저) + }) + @DisplayName("스트라이크와 볼 개수에 따라 정확한 한글 메시지를 생성한다") + void getResultMessageTest(int strike, int ball, String expected) { + String result = baseballService.getResultMessage(strike, ball); + assertThat(result).isEqualTo(expected); + } +} From 5678a92d875db7d0998651b134ee37f95f1a0823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EB=AC=B8=EC=A0=95=ED=99=98?= Date: Sun, 1 Feb 2026 16:30:50 +0900 Subject: [PATCH 18/18] =?UTF-8?q?feat:=20=EC=9E=AC=EC=8B=9C=EB=8F=84=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B2=80=EC=A6=9D=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 1, 2가 아닌 입력 시 예외처리 --- src/main/java/view/InputView.java | 2 +- src/test/java/view/InputViewTest.java | 18 ++++++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) create mode 100644 src/test/java/view/InputViewTest.java diff --git a/src/main/java/view/InputView.java b/src/main/java/view/InputView.java index c969227b..8b131061 100644 --- a/src/main/java/view/InputView.java +++ b/src/main/java/view/InputView.java @@ -15,7 +15,7 @@ public String readRestartInput() { return input; } - private void validateRestart(String input) { + void validateRestart(String input) { if (!input.equals("1") && !input.equals("2")) { throw new IllegalArgumentException("1 또는 2만 입력 가능합니다."); } diff --git a/src/test/java/view/InputViewTest.java b/src/test/java/view/InputViewTest.java new file mode 100644 index 00000000..e71bd1e1 --- /dev/null +++ b/src/test/java/view/InputViewTest.java @@ -0,0 +1,18 @@ +package view; + +import org.assertj.core.api.Assertions; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.ValueSource; + +public class InputViewTest { + + private final InputView inputView = new InputView(); + + @ParameterizedTest + @ValueSource(strings = {"3", "11", "22", " "}) + void validateRestartTest(String value){ + Assertions.assertThatThrownBy(() -> inputView.validateRestart(value)) + .isInstanceOf(IllegalArgumentException.class) + .hasMessage("1 또는 2만 입력 가능합니다."); + } +}