From b2115bc792763cc24f431d1c4ffc92e71de27396 Mon Sep 17 00:00:00 2001 From: youngho98 Date: Wed, 28 Jan 2026 18:29:45 +0900 Subject: [PATCH 01/10] =?UTF-8?q?docs:=20README=EC=97=90=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=20=EA=B3=84=ED=9A=8D=20=EB=B0=8F=20=EC=BB=A4=EB=B0=8B?= =?UTF-8?q?=20=EC=A0=84=EB=9E=B5=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 178 +++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 177 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d7e8aee..22791fa2 100644 --- a/README.md +++ b/README.md @@ -1 +1,177 @@ -# java-baseball-precourse \ No newline at end of file +# java-baseball-precourse + +## Commit 1. 컴퓨터 숫자 생성 규칙 구현 + +### 목표 + +컴퓨터가 사용하는 3자리 숫자의 생성 규칙을 도메인으로 분리한다. + +### 구현 내용 + +1~9 사이의 숫자 중 서로 다른 숫자 3개를 생성한다. + +생성된 숫자는 게임 도중 변경되지 않는다. + +랜덤 생성 로직은 별도의 생성 책임을 가지는 클래스로 분리한다. + +### 의도 + +숫자 생성 규칙을 UI 및 게임 흐름과 분리해 테스트 가능하도록 한다. + +## Commit 2. 판정 결과 모델(Hint) 구현 + +### 목표 + +스트라이크/볼 판정 결과를 표현하는 객체를 도입한다. + +### 구현 내용 + +strike, ball 값을 가진 불변 객체를 생성한다. + +3스트라이크 여부를 판단하는 책임을 가진다. + +스트라이크와 볼이 모두 0인 경우(낫싱)를 판단할 수 있다. + +### 의도 + +단순한 숫자 계산 결과를 의미 있는 도메인 객체로 표현한다. + +## Commit 3. 스트라이크/볼 판정 로직 구현 + +### 목표 + +컴퓨터 숫자와 사용자 입력을 비교하는 핵심 판정 로직을 구현한다. + +### 구현 내용 + +같은 숫자가 같은 자리에 있으면 스트라이크를 증가시킨다. + +같은 숫자가 다른 자리에 있으면 볼을 증가시킨다. + +판정 결과를 Hint 객체로 반환한다. + +### 의도 + +판정 로직을 Controller에서 분리하여 재사용성과 테스트 용이성을 확보한다. + +## Commit 4. 사용자 입력 파싱 및 검증 도메인 구현 + +### 목표 + +사용자 입력에 대한 모든 검증 로직을 도메인에서 처리한다. + +### 구현 내용 + +입력값은 문자열로 전달받아 도메인 객체로 변환한다. + +다음 조건을 만족하지 않으면 예외를 발생시킨다. + +길이가 3이 아닌 경우 + +숫자가 아닌 문자가 포함된 경우 + +1~9 범위를 벗어난 숫자가 있는 경우 (0 포함) + +중복된 숫자가 있는 경우 + +### 의도 + +“잘못된 입력”이라는 규칙을 UI나 Controller가 아닌 도메인에서 책임지도록 한다. + +## Commit 5. 도메인 단위 테스트 작성 + +### 목표 + +핵심 로직의 안정성을 단위 테스트로 검증한다. + +### 구현 내용 + +컴퓨터 숫자 생성 규칙 테스트 + +스트라이크/볼 판정 로직 테스트 + +사용자 입력 검증 테스트 + +JUnit5 + AssertJ 기반으로 테스트를 작성한다. + +UI 관련 코드는 테스트 대상에서 제외한다. + +### 의도 + +요구사항을 만족하는지 코드 레벨에서 명확히 검증한다. + +## Commit 6. 입출력(View) 구현 + +### 목표 + +사용자와의 입출력을 담당하는 View 계층을 구현한다. + +### 구현 내용 + +사용자 입력을 받는 기능을 분리한다. + +판정 결과를 요구사항에 맞는 형식으로 출력한다. + +잘못된 입력에 대해 [ERROR]로 시작하는 메시지를 출력한다. + +### 의도 + +출력 형식 변경이 도메인에 영향을 주지 않도록 한다. + +## Commit 7. 게임 진행 흐름(Controller) 구현 + +### 목표 + +전체 게임 흐름을 제어하는 Controller를 구현한다. + +### 구현 내용 + +숫자 입력 → 검증 → 판정 → 출력 과정을 반복한다. + +3스트라이크가 될 때까지 게임을 진행한다. + +도메인에서 발생한 예외를 처리해 게임이 종료되지 않도록 한다. + +### 의도 + +게임의 “흐름”과 “규칙”을 분리한다. + +## Commit 8. 게임 재시작 및 종료 처리 + +### 목표 + +게임 종료 후 재시작 또는 완전 종료 기능을 구현한다. + +### 구현 내용 + +게임 종료 후 선택지를 출력한다. + +1 입력 시 새로운 게임을 시작한다. + +2 입력 시 프로그램을 종료한다. + +잘못된 입력 시 예외를 발생시키고 다시 입력받는다. + +### 의도 + +한 번의 게임 로직을 재사용 가능한 구조로 만든다. + +## Commit 9. 제약사항 리팩토링 + +### 목표 + +모든 프로그래밍 요구사항을 최종적으로 만족시킨다. + +### 구현 내용 + +indent depth를 2 이하로 조정한다. + +else, switch/case 제거 + +메서드 길이 15라인 이하로 분리 + +하나의 메서드가 하나의 책임만 갖도록 리팩토링 + +### 의도 + +가독성과 유지보수성을 높인다. \ No newline at end of file From 23afee9fa0779ad04d7bdebde0f1c6ef3d3b8b37 Mon Sep 17 00:00:00 2001 From: youngho98 Date: Mon, 2 Feb 2026 21:45:31 +0900 Subject: [PATCH 02/10] =?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=20=EA=B7=9C=EC=B9=99=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../baseball/domain/NumbersGenerator.java | 35 +++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 src/main/java/baseball/domain/NumbersGenerator.java diff --git a/src/main/java/baseball/domain/NumbersGenerator.java b/src/main/java/baseball/domain/NumbersGenerator.java new file mode 100644 index 00000000..e3ec4d4c --- /dev/null +++ b/src/main/java/baseball/domain/NumbersGenerator.java @@ -0,0 +1,35 @@ +package baseball.domain; + +public class NumbersGenerator { + + private static final int SIZE = 3; + private static final int MIN = 1; + private static final int MAX = 9; + + public int[] generate() { + int[] numbers = new int[SIZE]; + for (int i = 0; i < SIZE; i++) { + int num = pickNumber(); + if (contains(numbers, i, num)) { + i--; + continue; + } + numbers[i] = num; + } + + return numbers; + } + + private int pickNumber() { + return (int) (Math.random() * (MAX - MIN + 1)) + MIN; + } + + private boolean contains(int[] numbers, int size, int candidate) { + for (int i = 0; i < size; i++) { + if (numbers[i] == candidate) { + return true; + } + } + return false; + } +} From 42aebb5ac6a9c1d6df8765a01cab1a1408f313f6 Mon Sep 17 00:00:00 2001 From: youngho98 Date: Mon, 2 Feb 2026 21:53:42 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20=EC=8A=A4=ED=8A=B8=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=ED=81=AC/=EB=B3=BC=20=ED=8C=90=EC=A0=95=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20=EB=AA=A8=EB=8D=B8(Hint)=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/baseball/domain/Hint.java | 28 +++++++++++++++++++++++++ 1 file changed, 28 insertions(+) create mode 100644 src/main/java/baseball/domain/Hint.java diff --git a/src/main/java/baseball/domain/Hint.java b/src/main/java/baseball/domain/Hint.java new file mode 100644 index 00000000..cfa6867c --- /dev/null +++ b/src/main/java/baseball/domain/Hint.java @@ -0,0 +1,28 @@ +package baseball.domain; + +public class Hint { + + private final int strike; + private final int ball; + + public Hint(int strike, int ball) { + this.strike = strike; + this.ball = ball; + } + + public int getStrike() { + return strike; + } + + public int getBall() { + return ball; + } + + public boolean isNothing() { + return strike == 0 && ball == 0; + } + + public boolean isThreeStrike() { + return strike == 3; + } +} From 561434032b520fcc80152d71edf3e09aa3136ae6 Mon Sep 17 00:00:00 2001 From: youngho98 Date: Thu, 5 Feb 2026 21:38:39 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat:=20=EC=8A=A4=ED=8A=B8=EB=9D=BC?= =?UTF-8?q?=EC=9D=B4=ED=81=AC/=EB=B3=BC=20=ED=8C=90=EC=A0=95=20=EB=A1=9C?= =?UTF-8?q?=EC=A7=81(Umpire)=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/baseball/domain/Umpire.java | 41 +++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 src/main/java/baseball/domain/Umpire.java diff --git a/src/main/java/baseball/domain/Umpire.java b/src/main/java/baseball/domain/Umpire.java new file mode 100644 index 00000000..9b280b5d --- /dev/null +++ b/src/main/java/baseball/domain/Umpire.java @@ -0,0 +1,41 @@ +package baseball.domain; + +public class Umpire { + + private static final int SIZE = 3; + + public Hint judge(int[] answer, int[] guess) { + int strike = countStrike(answer, guess); + int ball = countBall(answer, guess); + return new Hint(strike, ball); + } + + private int countStrike(int[] answer, int[] guess) { + int count = 0; + for (int i = 0; i < SIZE; i++) { + if (guess[i] == answer[i]) { + count++; + } + } + return count; + } + + private int countBall(int[] answer, int[] guess) { + int count = 0; + for (int i = 0; i < SIZE; i++) { + if (guess[i] != answer[i] && contains(answer, guess[i])) { + count++; + } + } + return count; + } + + private boolean contains(int[] numbers, int candidate) { + for (int n : numbers) { + if (candidate == n) { + return true; + } + } + return false; + } +} From 234b6604d2138f6060dfd4a394ec183eb6a6463f Mon Sep 17 00:00:00 2001 From: youngho98 Date: Thu, 5 Feb 2026 21:44:15 +0900 Subject: [PATCH 05/10] =?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=ED=8C=8C=EC=8B=B1=20=EB=B0=8F=20=EA=B2=80?= =?UTF-8?q?=EC=A6=9D=20=EB=8F=84=EB=A9=94=EC=9D=B8(PlayerGuess)=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/baseball/domain/PlayerGuess.java | 79 +++++++++++++++++++ 1 file changed, 79 insertions(+) create mode 100644 src/main/java/baseball/domain/PlayerGuess.java diff --git a/src/main/java/baseball/domain/PlayerGuess.java b/src/main/java/baseball/domain/PlayerGuess.java new file mode 100644 index 00000000..2ea43866 --- /dev/null +++ b/src/main/java/baseball/domain/PlayerGuess.java @@ -0,0 +1,79 @@ +package baseball.domain; + +public class PlayerGuess { + + private static final int SIZE = 3; + private static final int MIN = 1; + private static final int MAX = 9; + + private final int[] numbers; + + private PlayerGuess(int[] numbers) { + this.numbers = numbers; + } + + public static PlayerGuess from(String input) { + validateLength(input); + validateNumeric(input); + + int[] numbers = parse(input); + + validateRange(numbers); + validateUnique(numbers); + + return new PlayerGuess(numbers); + } + + public int numberAt(int index) { + return numbers[index]; + } + + public int size() { + return SIZE; + } + + public int[] asArray() { + return numbers.clone(); + } + + private static int[] parse(String input) { + int[] numbers = new int[SIZE]; + for (int i = 0; i < SIZE; i++) { + numbers[i] = input.charAt(i) - '0'; + } + return numbers; + } + + private static void validateLength(String input) { + if (input.length() != SIZE) { + throw new IllegalArgumentException("입력은 3자리여야 합니다."); + } + } + + private static void validateNumeric(String input) { + for (int i = 0; i < SIZE; i++) { + char c = input.charAt(i); + if (c < '0' || c > '9') { + throw new IllegalArgumentException("숫자만 입력해야 합니다."); + } + } + } + + private static void validateRange(int[] numbers) { + for (int n : numbers) { + if (n < MIN || n > MAX) { + throw new IllegalArgumentException("숫자는 1부터 9 사이여야 합니다."); + } + } + } + + private static void validateUnique(int[] numbers) { + for (int i = 0; i < SIZE; i++) { + for (int j = i + 1; j < SIZE; j++) { + if (numbers[i] == numbers[j]) { + throw new IllegalArgumentException("중복된 숫자는 허용되지 않습니다."); + } + } + } + } +} From 4cee254bf3e1aa8ec6c7ab1c94a9ed2f78af1b04 Mon Sep 17 00:00:00 2001 From: youngho98 Date: Fri, 6 Feb 2026 19:59:12 +0900 Subject: [PATCH 06/10] =?UTF-8?q?test:=20=EB=8F=84=EB=A9=94=EC=9D=B8=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EB=8B=A8=EC=9C=84=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../baseball/domain/NumbersGeneratorTest.java | 40 +++++++++++++++ .../java/baseball/domain/PlayerGuessTest.java | 40 +++++++++++++++ src/test/java/baseball/domain/UmpireTest.java | 50 +++++++++++++++++++ 3 files changed, 130 insertions(+) create mode 100644 src/test/java/baseball/domain/NumbersGeneratorTest.java create mode 100644 src/test/java/baseball/domain/PlayerGuessTest.java create mode 100644 src/test/java/baseball/domain/UmpireTest.java diff --git a/src/test/java/baseball/domain/NumbersGeneratorTest.java b/src/test/java/baseball/domain/NumbersGeneratorTest.java new file mode 100644 index 00000000..b7113fb6 --- /dev/null +++ b/src/test/java/baseball/domain/NumbersGeneratorTest.java @@ -0,0 +1,40 @@ +package baseball.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class NumbersGeneratorTest { + + @Test + void generate_는_서로다른_3개의_숫자를_생성한다() { + NumbersGenerator generator = new NumbersGenerator(); + + int[] numbers = generator.generate(); + + assertThat(numbers).hasSize(3); + assertThat(numbers[0]).isBetween(1, 9); + assertThat(numbers[1]).isBetween(1, 9); + assertThat(numbers[2]).isBetween(1, 9); + assertThat(numbers[0]).isNotEqualTo(numbers[1]); + assertThat(numbers[0]).isNotEqualTo(numbers[2]); + assertThat(numbers[1]).isNotEqualTo(numbers[2]); + } + + @Test + void generate_는_여러번_호출해도_항상_규칙을_만족한다() { + NumbersGenerator generator = new NumbersGenerator(); + + for (int i = 0; i < 100; i++) { + int[] numbers = generator.generate(); + + assertThat(numbers).hasSize(3); + assertThat(numbers[0]).isBetween(1, 9); + assertThat(numbers[1]).isBetween(1, 9); + assertThat(numbers[2]).isBetween(1, 9); + assertThat(numbers[0]).isNotEqualTo(numbers[1]); + assertThat(numbers[0]).isNotEqualTo(numbers[2]); + assertThat(numbers[1]).isNotEqualTo(numbers[2]); + } + } +} diff --git a/src/test/java/baseball/domain/PlayerGuessTest.java b/src/test/java/baseball/domain/PlayerGuessTest.java new file mode 100644 index 00000000..6982f6c0 --- /dev/null +++ b/src/test/java/baseball/domain/PlayerGuessTest.java @@ -0,0 +1,40 @@ +package baseball.domain; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class PlayerGuessTest { + + @Test + void 정상입력은_숫자배열로_변환된다() { + PlayerGuess guess = PlayerGuess.from("123"); + + assertThat(guess.asArray()).containsExactly(1, 2, 3); + } + + @Test + void 길이가_3이_아니면_예외() { + assertThatThrownBy(() -> PlayerGuess.from("12")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 숫자가_아닌_문자가_포함되면_예외() { + assertThatThrownBy(() -> PlayerGuess.from("1a3")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 숫자_0이_포함되면_예외() { + assertThatThrownBy(() -> PlayerGuess.from("102")) + .isInstanceOf(IllegalArgumentException.class); + } + + @Test + void 중복된_숫자가_있으면_예외() { + assertThatThrownBy(() -> PlayerGuess.from("112")) + .isInstanceOf(IllegalArgumentException.class); + } +} diff --git a/src/test/java/baseball/domain/UmpireTest.java b/src/test/java/baseball/domain/UmpireTest.java new file mode 100644 index 00000000..b8fa4bdd --- /dev/null +++ b/src/test/java/baseball/domain/UmpireTest.java @@ -0,0 +1,50 @@ +package baseball.domain; + +import static org.assertj.core.api.Assertions.assertThat; + +import org.junit.jupiter.api.Test; + +class UmpireTest { + + @Test + void 같은자리_같은숫자는_스트라이크다() { + Umpire umpire = new Umpire(); + + Hint hint = umpire.judge(new int[]{4, 2, 5}, new int[]{1, 2, 3}); + + assertThat(hint.getStrike()).isEqualTo(1); + assertThat(hint.getBall()).isEqualTo(0); + } + + @Test + void 다른자리_같은숫자는_볼이다() { + Umpire umpire = new Umpire(); + + Hint hint = umpire.judge(new int[]{4, 2, 5}, new int[]{4, 5, 6}); + + assertThat(hint.getStrike()).isEqualTo(1); + assertThat(hint.getBall()).isEqualTo(1); + } + + @Test + void 같은숫자가_전혀없으면_낫싱이다() { + Umpire umpire = new Umpire(); + + Hint hint = umpire.judge(new int[]{4, 2, 5}, new int[]{7, 8, 9}); + + assertThat(hint.getStrike()).isEqualTo(0); + assertThat(hint.getBall()).isEqualTo(0); + assertThat(hint.isNothing()).isTrue(); + } + + @Test + void 세자리_모두맞추면_3스트라이크다() { + Umpire umpire = new Umpire(); + + Hint hint = umpire.judge(new int[]{7, 1, 3}, new int[]{7, 1, 3}); + + assertThat(hint.isThreeStrike()).isTrue(); + assertThat(hint.getStrike()).isEqualTo(3); + assertThat(hint.getBall()).isEqualTo(0); + } +} From 98971ba6b6913e698e75d7a64ab4421c1592f7db Mon Sep 17 00:00:00 2001 From: youngho98 Date: Fri, 6 Feb 2026 20:19:42 +0900 Subject: [PATCH 07/10] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=9E=85=EC=B6=9C=EB=A0=A5=20View=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/baseball/view/InputView.java | 18 ++++++++++ src/main/java/baseball/view/OutputView.java | 37 +++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 src/main/java/baseball/view/InputView.java create mode 100644 src/main/java/baseball/view/OutputView.java diff --git a/src/main/java/baseball/view/InputView.java b/src/main/java/baseball/view/InputView.java new file mode 100644 index 00000000..f4b01619 --- /dev/null +++ b/src/main/java/baseball/view/InputView.java @@ -0,0 +1,18 @@ +package baseball.view; + +import java.util.Scanner; + +public class InputView { + + private static final Scanner SCANNER = new Scanner(System.in); + + public String readGuess() { + System.out.print("숫자를 입력해주세요 : "); + return SCANNER.nextLine(); + } + + public String readRestartCommand() { + System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."); + return SCANNER.nextLine(); + } +} diff --git a/src/main/java/baseball/view/OutputView.java b/src/main/java/baseball/view/OutputView.java new file mode 100644 index 00000000..ac4d9bfa --- /dev/null +++ b/src/main/java/baseball/view/OutputView.java @@ -0,0 +1,37 @@ +package baseball.view; + +import baseball.domain.Hint; + +public class OutputView { + + public void printHint(Hint hint) { + if (hint.isNothing()) { + System.out.println("낫싱"); + return; + } + + printStrike(hint); + printBall(hint); + System.out.println(); + } + + public void printErrorMessage() { + System.out.println("[ERROR] 잘못된 입력입니다."); + } + + public void printGameEnd() { + System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 끝"); + } + + private void printStrike(Hint hint) { + if (hint.getStrike() > 0) { + System.out.print(hint.getStrike() + "스트라이크 "); + } + } + + private void printBall(Hint hint) { + if (hint.getBall() > 0) { + System.out.print(hint.getBall() + "볼 "); + } + } +} From 2e1950d81b336e06d6e55cdb3450f75193dcbc4f Mon Sep 17 00:00:00 2001 From: youngho98 Date: Sat, 7 Feb 2026 14:09:12 +0900 Subject: [PATCH 08/10] =?UTF-8?q?feat:=20=EA=B2=8C=EC=9E=84=20=EC=A7=84?= =?UTF-8?q?=ED=96=89=20=ED=9D=90=EB=A6=84=20Controller=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../baseball/controller/GameController.java | 58 +++++++++++++++++++ 1 file changed, 58 insertions(+) create mode 100644 src/main/java/baseball/controller/GameController.java diff --git a/src/main/java/baseball/controller/GameController.java b/src/main/java/baseball/controller/GameController.java new file mode 100644 index 00000000..95e9489b --- /dev/null +++ b/src/main/java/baseball/controller/GameController.java @@ -0,0 +1,58 @@ +package baseball.controller; + +import baseball.domain.Hint; +import baseball.domain.NumbersGenerator; +import baseball.domain.PlayerGuess; +import baseball.domain.Umpire; +import baseball.view.InputView; +import baseball.view.OutputView; + +public class GameController { + + private final NumbersGenerator numbersGenerator; + private final Umpire umpire; + private final InputView inputView; + private final OutputView outputView; + + public GameController(NumbersGenerator numbersGenerator, Umpire umpire, + InputView inputView, OutputView outputView) { + this.numbersGenerator = numbersGenerator; + this.umpire = umpire; + this.inputView = inputView; + this.outputView = outputView; + } + + public void play() { + int[] answer = numbersGenerator.generate(); + + while (true) { + Hint hint = playOneTurn(answer); + + if (hint == null) { + continue; + } + + if (hint.isThreeStrike()) { + outputView.printGameEnd(); + return; + } + } + } + + private Hint playOneTurn(int[] answer) { + try { + PlayerGuess guess = readGuess(); + Hint hint = umpire.judge(answer, guess.asArray()); + outputView.printHint(hint); + return hint; + } catch (IllegalArgumentException e) { + outputView.printErrorMessage(); + return null; + } + } + + private PlayerGuess readGuess() { + String input = inputView.readGuess(); + return PlayerGuess.from(input); + } +} From 91dbabe61753f2a35d8eb407b8b4e941f0fb3732 Mon Sep 17 00:00:00 2001 From: youngho98 Date: Sat, 7 Feb 2026 14:35:48 +0900 Subject: [PATCH 09/10] =?UTF-8?q?feat:=20=EA=B2=8C=EC=9E=84=20=EC=9E=AC?= =?UTF-8?q?=EC=8B=9C=EC=9E=91=20=EB=B0=8F=20=EC=A2=85=EB=A3=8C=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=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/baseball/Application.java | 21 ++++++++++++ .../baseball/controller/GameController.java | 32 ++++++++++++++++++- 2 files changed, 52 insertions(+), 1 deletion(-) create mode 100644 src/main/java/baseball/Application.java diff --git a/src/main/java/baseball/Application.java b/src/main/java/baseball/Application.java new file mode 100644 index 00000000..a5b92948 --- /dev/null +++ b/src/main/java/baseball/Application.java @@ -0,0 +1,21 @@ +package baseball; + +import baseball.controller.GameController; +import baseball.domain.NumbersGenerator; +import baseball.domain.Umpire; +import baseball.view.InputView; +import baseball.view.OutputView; + +public class Application { + + public static void main(String[] args) { + GameController gameController = new GameController( + new NumbersGenerator(), + new Umpire(), + new InputView(), + new OutputView() + ); + + gameController.run(); + } +} diff --git a/src/main/java/baseball/controller/GameController.java b/src/main/java/baseball/controller/GameController.java index 95e9489b..01979a48 100644 --- a/src/main/java/baseball/controller/GameController.java +++ b/src/main/java/baseball/controller/GameController.java @@ -22,7 +22,18 @@ public GameController(NumbersGenerator numbersGenerator, Umpire umpire, this.outputView = outputView; } - public void play() { + public void run() { + while (true) { + playOneGame(); + + if (isRestart()) { + continue; + } + return; + } + } + + public void playOneGame() { int[] answer = numbersGenerator.generate(); while (true) { @@ -55,4 +66,23 @@ private PlayerGuess readGuess() { String input = inputView.readGuess(); return PlayerGuess.from(input); } + + private boolean isRestart() { + try { + String input = inputView.readRestartCommand(); + + if ("1".equals(input)) { + return true; + } + + if ("2".equals(input)) { + return false; + } + + throw new IllegalArgumentException(); + } catch (IllegalArgumentException e) { + outputView.printErrorMessage(); + return isRestart(); + } + } } From 6873c16685a4ff6386d62c23064363e6255e3ed2 Mon Sep 17 00:00:00 2001 From: youngho98 Date: Sat, 7 Feb 2026 15:25:41 +0900 Subject: [PATCH 10/10] =?UTF-8?q?refactor:=20=EC=A0=9C=EC=95=BD=EC=82=AC?= =?UTF-8?q?=ED=95=AD=20=EC=A4=80=EC=88=98=EB=A5=BC=20=EC=9C=84=ED=95=9C=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=A0=95=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../baseball/controller/GameController.java | 25 +++++++------ .../baseball/domain/NumbersGenerator.java | 10 ++--- .../java/baseball/domain/PlayerGuess.java | 26 ++++--------- src/main/java/baseball/domain/Umpire.java | 6 +-- src/main/java/baseball/view/OutputView.java | 37 ++++++++++++------- 5 files changed, 49 insertions(+), 55 deletions(-) diff --git a/src/main/java/baseball/controller/GameController.java b/src/main/java/baseball/controller/GameController.java index 01979a48..d6d28a2f 100644 --- a/src/main/java/baseball/controller/GameController.java +++ b/src/main/java/baseball/controller/GameController.java @@ -33,7 +33,7 @@ public void run() { } } - public void playOneGame() { + private void playOneGame() { int[] answer = numbersGenerator.generate(); while (true) { @@ -70,19 +70,22 @@ private PlayerGuess readGuess() { private boolean isRestart() { try { String input = inputView.readRestartCommand(); - - if ("1".equals(input)) { - return true; - } - - if ("2".equals(input)) { - return false; - } - - throw new IllegalArgumentException(); + return parseRestartCommand(input); } catch (IllegalArgumentException e) { outputView.printErrorMessage(); return isRestart(); } } + + private boolean parseRestartCommand(String input) { + if ("1".equals(input)) { + return true; + } + + if ("2".equals(input)) { + return false; + } + + throw new IllegalArgumentException(); + } } diff --git a/src/main/java/baseball/domain/NumbersGenerator.java b/src/main/java/baseball/domain/NumbersGenerator.java index e3ec4d4c..ca22af3d 100644 --- a/src/main/java/baseball/domain/NumbersGenerator.java +++ b/src/main/java/baseball/domain/NumbersGenerator.java @@ -2,13 +2,9 @@ public class NumbersGenerator { - private static final int SIZE = 3; - private static final int MIN = 1; - private static final int MAX = 9; - public int[] generate() { - int[] numbers = new int[SIZE]; - for (int i = 0; i < SIZE; i++) { + int[] numbers = new int[3]; + for (int i = 0; i < 3; i++) { int num = pickNumber(); if (contains(numbers, i, num)) { i--; @@ -21,7 +17,7 @@ public int[] generate() { } private int pickNumber() { - return (int) (Math.random() * (MAX - MIN + 1)) + MIN; + return (int) (Math.random() * 9) + 1; } private boolean contains(int[] numbers, int size, int candidate) { diff --git a/src/main/java/baseball/domain/PlayerGuess.java b/src/main/java/baseball/domain/PlayerGuess.java index 2ea43866..dd6b6372 100644 --- a/src/main/java/baseball/domain/PlayerGuess.java +++ b/src/main/java/baseball/domain/PlayerGuess.java @@ -2,10 +2,6 @@ public class PlayerGuess { - private static final int SIZE = 3; - private static final int MIN = 1; - private static final int MAX = 9; - private final int[] numbers; private PlayerGuess(int[] numbers) { @@ -24,34 +20,26 @@ public static PlayerGuess from(String input) { return new PlayerGuess(numbers); } - public int numberAt(int index) { - return numbers[index]; - } - - public int size() { - return SIZE; - } - public int[] asArray() { return numbers.clone(); } private static int[] parse(String input) { - int[] numbers = new int[SIZE]; - for (int i = 0; i < SIZE; i++) { + int[] numbers = new int[3]; + for (int i = 0; i < 3; i++) { numbers[i] = input.charAt(i) - '0'; } return numbers; } private static void validateLength(String input) { - if (input.length() != SIZE) { + if (input.length() != 3) { throw new IllegalArgumentException("입력은 3자리여야 합니다."); } } private static void validateNumeric(String input) { - for (int i = 0; i < SIZE; i++) { + for (int i = 0; i < 3; i++) { char c = input.charAt(i); if (c < '0' || c > '9') { throw new IllegalArgumentException("숫자만 입력해야 합니다."); @@ -61,15 +49,15 @@ private static void validateNumeric(String input) { private static void validateRange(int[] numbers) { for (int n : numbers) { - if (n < MIN || n > MAX) { + if (n < 1 || n > 9) { throw new IllegalArgumentException("숫자는 1부터 9 사이여야 합니다."); } } } private static void validateUnique(int[] numbers) { - for (int i = 0; i < SIZE; i++) { - for (int j = i + 1; j < SIZE; j++) { + for (int i = 0; i < 3; i++) { + for (int j = i + 1; j < 3; j++) { if (numbers[i] == numbers[j]) { throw new IllegalArgumentException("중복된 숫자는 허용되지 않습니다."); } diff --git a/src/main/java/baseball/domain/Umpire.java b/src/main/java/baseball/domain/Umpire.java index 9b280b5d..9915585d 100644 --- a/src/main/java/baseball/domain/Umpire.java +++ b/src/main/java/baseball/domain/Umpire.java @@ -2,8 +2,6 @@ public class Umpire { - private static final int SIZE = 3; - public Hint judge(int[] answer, int[] guess) { int strike = countStrike(answer, guess); int ball = countBall(answer, guess); @@ -12,7 +10,7 @@ public Hint judge(int[] answer, int[] guess) { private int countStrike(int[] answer, int[] guess) { int count = 0; - for (int i = 0; i < SIZE; i++) { + for (int i = 0; i < 3; i++) { if (guess[i] == answer[i]) { count++; } @@ -22,7 +20,7 @@ private int countStrike(int[] answer, int[] guess) { private int countBall(int[] answer, int[] guess) { int count = 0; - for (int i = 0; i < SIZE; i++) { + for (int i = 0; i < 3; i++) { if (guess[i] != answer[i] && contains(answer, guess[i])) { count++; } diff --git a/src/main/java/baseball/view/OutputView.java b/src/main/java/baseball/view/OutputView.java index ac4d9bfa..e3d4cf2a 100644 --- a/src/main/java/baseball/view/OutputView.java +++ b/src/main/java/baseball/view/OutputView.java @@ -10,28 +10,37 @@ public void printHint(Hint hint) { return; } - printStrike(hint); - printBall(hint); - System.out.println(); + System.out.println(buildHintMessage(hint)); } - public void printErrorMessage() { - System.out.println("[ERROR] 잘못된 입력입니다."); - } - - public void printGameEnd() { - System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 끝"); + private String buildHintMessage(Hint hint) { + StringBuilder sb = new StringBuilder(); + appendStrike(sb, hint); + appendBall(sb, hint); + return sb.toString(); } - private void printStrike(Hint hint) { + private void appendStrike(StringBuilder sb, Hint hint) { if (hint.getStrike() > 0) { - System.out.print(hint.getStrike() + "스트라이크 "); + sb.append(hint.getStrike()).append("스트라이크"); } } - private void printBall(Hint hint) { - if (hint.getBall() > 0) { - System.out.print(hint.getBall() + "볼 "); + private void appendBall(StringBuilder sb, Hint hint) { + if (hint.getBall() == 0) { + return; + } + if (sb.length() > 0) { + sb.append(" "); } + sb.append(hint.getBall()).append("볼"); + } + + public void printErrorMessage() { + System.out.println("[ERROR] 잘못된 입력입니다."); + } + + public void printGameEnd() { + System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 끝"); } }