From e73f9bb0a998a5fbca9265a4c4917e939a75ac6e Mon Sep 17 00:00:00 2001 From: greemi17 Date: Wed, 28 Jan 2026 18:17:01 +0900 Subject: [PATCH 1/8] =?UTF-8?q?style:=20=EC=BD=94=EB=93=9C=20=ED=8F=AC?= =?UTF-8?q?=EB=A7=B7=ED=8C=85=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 29 +++++++++++++++++++++++++++++ .gitattributes | 10 ++++++++++ build.gradle | 4 +++- 3 files changed, 42 insertions(+), 1 deletion(-) create mode 100644 .editorconfig create mode 100644 .gitattributes diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..e72c072d --- /dev/null +++ b/.editorconfig @@ -0,0 +1,29 @@ +# top-most EditorConfig file +root = true + +[*] +# [encoding-utf8] +charset = utf-8 + +# [newline-lf] +end_of_line = lf + +# [newline-eof] +insert_final_newline = true + +[*.bat] +end_of_line = crlf + +[*.java] +# [indentation-tab] +indent_style = tab + +# [4-spaces-tab] +indent_size = 4 +tab_width = 4 + +# [no-trailing-spaces] +trim_trailing_whitespace = true + +[line-length-120] +max_line_length = 120 \ No newline at end of file diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 00000000..9423ff36 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,10 @@ +*.c text eol=lf +*.cpp text eol=lf +*.h text eol=lf + +# exception for visual studio project configuration +*.sln text eol=crlf +*.vs text eol=crlf +*.csproj eol=crlf +*.props eol=crlf +*.filters eol=crlf diff --git a/build.gradle b/build.gradle index 20a92c9e..4b5ee602 100644 --- a/build.gradle +++ b/build.gradle @@ -1,7 +1,9 @@ plugins { id 'java' } - +// 방법1 +compileJava.options.encoding = 'UTF-8' +compileTestJava.options.encoding = 'UTF-8' group = 'camp.nextstep.edu' version = '1.0-SNAPSHOT' From 9d68832ece100dedc7bb6fa7979cf623833b187b Mon Sep 17 00:00:00 2001 From: greemi17 Date: Wed, 28 Jan 2026 18:27:34 +0900 Subject: [PATCH 2/8] =?UTF-8?q?docs:=20=EA=B8=B0=EB=8A=A5=20=EC=9A=94?= =?UTF-8?q?=EA=B5=AC=20=EC=82=AC=ED=95=AD=20=EC=A0=95=EB=A6=AC=20=EB=B0=8F?= =?UTF-8?q?=20=EA=B5=AC=ED=98=84=20=EA=B3=84=ED=9A=8D=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 게임의 핵심 로직과 인터페이스 제약 사항을 구체화하고 구현 순서를 정비 [적용 사항] 1. 핵심 로직 상세 정의 - 정답 생성, 숫자 비교(스트라이크/볼/낫싱), 승리 조건 명시 2. 인터페이스 제약 사항 추가 - 입력값 범위(1~9, 3자리, 중복 없음) 및 예외 처리(IllegalArgumentException) 기준 확립 3. 커밋 계획 세분화 - 도메인, 뷰, 컨트롤러 계층으로 분리하여 TDD 흐름에 맞게 작업 순서 조정 [적용 이유] 개발 요구 사항을 명확히 하고 체계적인 구현 가이드를 제공하기 위함이다. Resolves: #2 --- README.md | 69 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 68 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d7e8aee..63b46a84 100644 --- a/README.md +++ b/README.md @@ -1 +1,68 @@ -# java-baseball-precourse \ No newline at end of file +# java-baseball-precourse + +> 컴퓨터가 제시하는 임의의 숫자 3개를 모두 맞히는게 목표인 게임 +> +> 맞힐 때마다 라운드가 진행되며, 라운드 제한은 없음 +> +> 라운드 진행 후, 각 세부 규칙마다 볼, 스트라이크를 출력 +> 스트라이크 = 같은 수 + 같은 자리 +> +> 볼 = 같은 수 + 다른 자리 + +## 핵심 로직 + +- 정답 생성: 1에서 9까지 서로 다른 임의의 수 3개 생성. + +- 숫자 비교: 사용자가 입력한 수와 컴퓨터의 수를 비교. + +- 같은 수 + 같은 자리 = 스트라이크 + +- 같은 수 + 다른 자리 = 볼 + +- 일치하는 숫자가 전혀 없음 = 낫싱 + +- 결과 출력: 계산된 힌트(스트라이크, 볼, 낫싱)를 형식에 맞춰 출력. + +- 승리 조건: 3개의 숫자를 모두 맞히면 게임 종료 및 재시작/종료 선택창 출력. + +### 인터페이스 제약 사항 + +**입력** + +- 서로 다른 3자리의 수 (각 자리는 1~9) +- 게임 재시작/종료 선택 시 1(재시작) 또는 2(종료) +- 잘못된 값 입력 시 `IllegalArgumentException` 발생 + +**출력** + +- 입력한 수에 대한 결과를 볼, 스트라이크 개수로 표시 +- 하나도 없는 경우 "낫싱" +- 3개의 숫자를 모두 맞힐 경우 게임 종료 문구 출력 + +## 커밋 계획 + +### 사전 구성 + +1. style: 코드 포맷팅 구성 ✅ +2. docs: 기능 요구 사항 정리 및 구현 계획 작성 +3. chore: junit 학습 테스트 실행 및 적용 + +### 핵심 로직 (Domain) + +4. test: 정답 숫자 생성 기능 테스트 +5. feat: 1~9 서로 다른 임의의 수 3개 생성 구현 (Green) +6. test: 볼, 스트라이크 비교 로직 테스트 +7. feat: 볼, 스트라이크 비교 및 판별 로직 구현 (Green) +8. refactor: 비교 로직 가독성 개선 (Refactor) + +### 입출력 및 유효성 검사 (View/Validation) + +9. test: 유저 숫자 입력 유효성 검사 (3자리 숫자, 중복 확인) +10. feat: 유저 입력 파싱 및 예외 처리 구현 (Green) +11. test: 게임 재시작/종료 입력 검증 +12. feat: 재시작(1)/종료(2) 입력 처리 구현 (Green) + +### 게임 진행 (Controller) + +13. feat: 게임 흐름 제어 및 메인 루프 구현 +14. refactor: 전체 구조 리팩토링 및 메소드 분리 (Refactor) From 40a9a4cb3691e4769c92ff7348667bbf4baf2fc4 Mon Sep 17 00:00:00 2001 From: TaeYoon17 Date: Mon, 2 Feb 2026 21:26:04 +0900 Subject: [PATCH 3/8] =?UTF-8?q?chore:=20junit=20=ED=95=99=EC=8A=B5=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=8B=A4=ED=96=89=20=EB=B0=8F=20?= =?UTF-8?q?=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit junit 학습 테스트 실행 및 적용 [적용 사항] junit 테스트 파일 추가 - @BeforeEach 어노테이션 실습 - assertThat 조건 학습 [적용 이유] 단위 테스트 적용을 위한 연습 --- build.gradle | 2 ++ src/test/java/study/StringTest.java | 53 +++++++++++++++++++++++++++++ 2 files changed, 55 insertions(+) create mode 100644 src/test/java/study/StringTest.java diff --git a/build.gradle b/build.gradle index 4b5ee602..0df928b1 100644 --- a/build.gradle +++ b/build.gradle @@ -20,8 +20,10 @@ repositories { dependencies { testImplementation 'org.junit.jupiter:junit-jupiter:5.10.2' testImplementation 'org.assertj:assertj-core:3.25.3' + testImplementation 'org.junit.jupiter:junit-jupiter-params:5.11.0' } + test { useJUnitPlatform() } diff --git a/src/test/java/study/StringTest.java b/src/test/java/study/StringTest.java new file mode 100644 index 00000000..e199bf86 --- /dev/null +++ b/src/test/java/study/StringTest.java @@ -0,0 +1,53 @@ +package study; + +import static org.assertj.core.api.AssertionsForClassTypes.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.junit.platform.commons.util.StringUtils.*; + +import java.util.HashSet; +import java.util.Set; + +import org.junit.jupiter.api.BeforeEach; +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 StringTest { + + private Set numbers; + + @Test + void name() { + String name = "모두 반갑습니다."; + } + + @BeforeEach + void setUp() { + numbers = new HashSet<>(); + numbers.add(1); + numbers.add(2); + numbers.add(3); + } + + @Test + void contains() { + assertThat(numbers.contains(1)).isTrue(); + assertThat(numbers.contains(2)).isTrue(); + assertThat(numbers.contains(3)).isTrue(); + } + + @ParameterizedTest + @ValueSource(strings = {"", " "}) + void isBlank_ShouldReturnTrueForNullOrBlankStrings(String input) { + assertTrue(isBlank(input)); + } + + @ParameterizedTest + @CsvSource(value = {"test:test", "tEst:test", "Java:java"}, delimiter = ':') + void toLowerCase_ShouldGenerateTheExpectedLowercaseValue(String input, String expected) { + String actualValue = input.toLowerCase(); + assertEquals(expected, actualValue); + } + +} From 6abc6f1ec579297dba29c2170a720718b9be703d Mon Sep 17 00:00:00 2001 From: TaeYoon17 Date: Mon, 2 Feb 2026 22:23:15 +0900 Subject: [PATCH 4/8] =?UTF-8?q?feat:=201~9=20=EC=84=9C=EB=A1=9C=20?= =?UTF-8?q?=EB=8B=A4=EB=A5=B8=20=EC=9E=84=EC=9D=98=EC=9D=98=20=EC=88=98=20?= =?UTF-8?q?3=EA=B0=9C=20=EC=83=9D=EC=84=B1=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 난수 생성 도메인 로직 구성 [적용 사항] - INumberGenerator 인터페이스 기반 IntNumberGenerator 구현체 작성 - 1~9 사이의 서로 다른 임의의 수 3개를 생성하는 로직 구현 - `Date` 시드를 활용한 난수 생성 테스트 코드(`NumberGeneratorTest`) 작성 [적용 이유] - 핵심 로직인 정답 숫자 생성을 위함 - 일관된 테스트 환경을 구축 --- README.md | 25 +++++------ .../number_generator/INumberGenerator.java | 7 +++ .../number_generator/IntNumberGenerator.java | 22 +++++++++ .../baseball/domain/NumberGeneratorTest.java | 45 +++++++++++++++++++ 4 files changed, 86 insertions(+), 13 deletions(-) create mode 100644 src/main/java/baseball/domain/number_generator/INumberGenerator.java create mode 100644 src/main/java/baseball/domain/number_generator/IntNumberGenerator.java create mode 100644 src/test/java/baseball/domain/NumberGeneratorTest.java diff --git a/README.md b/README.md index 63b46a84..11a1e112 100644 --- a/README.md +++ b/README.md @@ -44,25 +44,24 @@ ### 사전 구성 1. style: 코드 포맷팅 구성 ✅ -2. docs: 기능 요구 사항 정리 및 구현 계획 작성 -3. chore: junit 학습 테스트 실행 및 적용 +2. docs: 기능 요구 사항 정리 및 구현 계획 작성 ✅ +3. chore: junit 학습 테스트 실행 및 적용 ✅ ### 핵심 로직 (Domain) -4. test: 정답 숫자 생성 기능 테스트 -5. feat: 1~9 서로 다른 임의의 수 3개 생성 구현 (Green) -6. test: 볼, 스트라이크 비교 로직 테스트 -7. feat: 볼, 스트라이크 비교 및 판별 로직 구현 (Green) -8. refactor: 비교 로직 가독성 개선 (Refactor) +4. feat: 1~9 서로 다른 임의의 수 3개 생성 구현 (Green) ✅ +5. test: 볼, 스트라이크 비교 로직 테스트 +6. feat: 볼, 스트라이크 비교 및 판별 로직 구현 (Green) +7. refactor: 비교 로직 가독성 개선 (Refactor) ### 입출력 및 유효성 검사 (View/Validation) -9. test: 유저 숫자 입력 유효성 검사 (3자리 숫자, 중복 확인) -10. feat: 유저 입력 파싱 및 예외 처리 구현 (Green) -11. test: 게임 재시작/종료 입력 검증 -12. feat: 재시작(1)/종료(2) 입력 처리 구현 (Green) +8. test: 유저 숫자 입력 유효성 검사 (3자리 숫자, 중복 확인) +9. feat: 유저 입력 파싱 및 예외 처리 구현 (Green) +10. test: 게임 재시작/종료 입력 검증 +11. feat: 재시작(1)/종료(2) 입력 처리 구현 (Green) ### 게임 진행 (Controller) -13. feat: 게임 흐름 제어 및 메인 루프 구현 -14. refactor: 전체 구조 리팩토링 및 메소드 분리 (Refactor) +12. feat: 게임 흐름 제어 및 메인 루프 구현 +13. refactor: 전체 구조 리팩토링 및 메소드 분리 (Refactor) diff --git a/src/main/java/baseball/domain/number_generator/INumberGenerator.java b/src/main/java/baseball/domain/number_generator/INumberGenerator.java new file mode 100644 index 00000000..a321102f --- /dev/null +++ b/src/main/java/baseball/domain/number_generator/INumberGenerator.java @@ -0,0 +1,7 @@ +package baseball.domain.number_generator; + +import java.util.Date; + +public interface INumberGenerator { + T execute(Date date); +} diff --git a/src/main/java/baseball/domain/number_generator/IntNumberGenerator.java b/src/main/java/baseball/domain/number_generator/IntNumberGenerator.java new file mode 100644 index 00000000..302db9ac --- /dev/null +++ b/src/main/java/baseball/domain/number_generator/IntNumberGenerator.java @@ -0,0 +1,22 @@ +package baseball.domain.number_generator; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.Random; + +public class IntNumberGenerator implements INumberGenerator> { + + @Override + public List execute(Date date) { + Random random = new Random(date.getTime()); + List numbers = new ArrayList<>(); + while (numbers.size() < 3) { + int number = random.nextInt(9) + 1; + if (numbers.contains(number)) + continue; + numbers.add(number); + } + return numbers; + } +} diff --git a/src/test/java/baseball/domain/NumberGeneratorTest.java b/src/test/java/baseball/domain/NumberGeneratorTest.java new file mode 100644 index 00000000..044e69e8 --- /dev/null +++ b/src/test/java/baseball/domain/NumberGeneratorTest.java @@ -0,0 +1,45 @@ +package baseball.domain; + +import static org.assertj.core.api.Assertions.*; + +import java.util.Date; +import java.util.HashSet; +import java.util.List; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import baseball.domain.number_generator.INumberGenerator; +import baseball.domain.number_generator.IntNumberGenerator; + +public class NumberGeneratorTest { + private final INumberGenerator> numberGenerator = new IntNumberGenerator(); + + @DisplayName("1에서 9까지 서로 다른 임의의 수 3개를 생성한다") + @Test + void generateIntNumbers() { + // given + Date now = new Date(); + + // when + List generatedNumbers = numberGenerator.execute(now); + + // then + assertThat(generatedNumbers).hasSize(3); + assertThat(generatedNumbers).allMatch(number -> number >= 1 && number <= 9); + } + + @DisplayName("고정된 날짜(시드)를 입력하면 항상 동일한 숫자 목록을 반환한다") + @Test + void generateFixedNumbers() { + // given + Date fixedDate = new Date(1000L); // 고정된 타임스탬프 사용 + + // when + List result1 = numberGenerator.execute(fixedDate); + List result2 = numberGenerator.execute(fixedDate); + + // then + assertThat(result1).isEqualTo(result2); + } +} From 73020002525bb11d2eaa348f2651f7d837a25095 Mon Sep 17 00:00:00 2001 From: TaeYoon17 Date: Tue, 3 Feb 2026 00:00:01 +0900 Subject: [PATCH 5/8] =?UTF-8?q?feat:=20=EB=B3=BC,=20=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=EB=9D=BC=EC=9D=B4=ED=81=AC=20=EB=B9=84=EA=B5=90=20=EB=B0=8F=20?= =?UTF-8?q?=ED=8C=90=EB=B3=84=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 [적용 사항] - IScoreCalculator 인터페이스 기반 StrikeCalculator, BallCalculator 구현 - 스트라이크(같은 수, 같은 자리) 판별 로직 구현 - 볼(같은 수, 다른 자리) 판별 로직 구현 - BallCalculator가 StrikeCalculator를 의존하도록 구성하여, 볼 계산 시 스트라이크 수를 제외 - @ParameterizedTest를 활용하여 다양한 케이스에 대한 테스트 코드(ScoreCalculatorTest) 작성 [적용 이유] - 사용자가 입력한 숫자와 컴퓨터의 숫자를 비교하여 힌트를 제공하는 핵심 로직을 구현, 연산이 아닌 계산식으로 표현하여 의존성 분리를 위함 - 단위 테스트를 통해 볼, 스트라이크 판별 로직의 정확성을 검증하고 안정성을 확보하기 위함 --- .../number_generator/INumberGenerator.java | 2 +- .../number_generator/IntNumberGenerator.java | 6 +- .../BallCalculator.java | 26 ++++++++ .../IScoreCalculator.java | 7 +++ .../StrikeCalculator.java | 19 ++++++ .../NumberGeneratorTest.java | 13 ++-- .../score_calculator/ScoreCalculatorTest.java | 61 +++++++++++++++++++ 7 files changed, 122 insertions(+), 12 deletions(-) create mode 100644 src/main/java/baseball/domain/strike_zone_calculator/BallCalculator.java create mode 100644 src/main/java/baseball/domain/strike_zone_calculator/IScoreCalculator.java create mode 100644 src/main/java/baseball/domain/strike_zone_calculator/StrikeCalculator.java rename src/test/java/baseball/domain/{ => number_generator}/NumberGeneratorTest.java (71%) create mode 100644 src/test/java/baseball/domain/score_calculator/ScoreCalculatorTest.java diff --git a/src/main/java/baseball/domain/number_generator/INumberGenerator.java b/src/main/java/baseball/domain/number_generator/INumberGenerator.java index a321102f..67b25ef9 100644 --- a/src/main/java/baseball/domain/number_generator/INumberGenerator.java +++ b/src/main/java/baseball/domain/number_generator/INumberGenerator.java @@ -3,5 +3,5 @@ import java.util.Date; public interface INumberGenerator { - T execute(Date date); + T execute(Date date, Integer generateNumberCount); } diff --git a/src/main/java/baseball/domain/number_generator/IntNumberGenerator.java b/src/main/java/baseball/domain/number_generator/IntNumberGenerator.java index 302db9ac..79353836 100644 --- a/src/main/java/baseball/domain/number_generator/IntNumberGenerator.java +++ b/src/main/java/baseball/domain/number_generator/IntNumberGenerator.java @@ -6,12 +6,12 @@ import java.util.Random; public class IntNumberGenerator implements INumberGenerator> { - + @Override - public List execute(Date date) { + public List execute(Date date, Integer generateNumberCount) { Random random = new Random(date.getTime()); List numbers = new ArrayList<>(); - while (numbers.size() < 3) { + while (numbers.size() < generateNumberCount) { int number = random.nextInt(9) + 1; if (numbers.contains(number)) continue; diff --git a/src/main/java/baseball/domain/strike_zone_calculator/BallCalculator.java b/src/main/java/baseball/domain/strike_zone_calculator/BallCalculator.java new file mode 100644 index 00000000..8c1f18e9 --- /dev/null +++ b/src/main/java/baseball/domain/strike_zone_calculator/BallCalculator.java @@ -0,0 +1,26 @@ +package baseball.domain.strike_zone_calculator; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class BallCalculator implements IScoreCalculator { + + private final IScoreCalculator strikeCalculator; + + public BallCalculator(IScoreCalculator strikeCalculator) { + this.strikeCalculator = strikeCalculator; + } + + @Override + public int calculate(List human, List computer) { + if (human.size() != computer.size()) { + throw new IllegalArgumentException("두 수의 갯수가 다릅니다!"); + } + Set humanSet = new HashSet<>(human); + Set computerSet = new HashSet<>(computer); + + humanSet.retainAll(computerSet); + return Integer.max(0, humanSet.size() - strikeCalculator.calculate(human, computer)); + } +} diff --git a/src/main/java/baseball/domain/strike_zone_calculator/IScoreCalculator.java b/src/main/java/baseball/domain/strike_zone_calculator/IScoreCalculator.java new file mode 100644 index 00000000..d1e6addf --- /dev/null +++ b/src/main/java/baseball/domain/strike_zone_calculator/IScoreCalculator.java @@ -0,0 +1,7 @@ +package baseball.domain.strike_zone_calculator; + +import java.util.List; + +public interface IScoreCalculator { + int calculate(List human, List computer); +} diff --git a/src/main/java/baseball/domain/strike_zone_calculator/StrikeCalculator.java b/src/main/java/baseball/domain/strike_zone_calculator/StrikeCalculator.java new file mode 100644 index 00000000..a14b69c0 --- /dev/null +++ b/src/main/java/baseball/domain/strike_zone_calculator/StrikeCalculator.java @@ -0,0 +1,19 @@ +package baseball.domain.strike_zone_calculator; + +import java.util.List; + +public class StrikeCalculator implements IScoreCalculator { + @Override + public int calculate(List human, List computer) { + if (human.size() != computer.size()) { + throw new IllegalArgumentException("두 수의 갯수가 다릅니다!"); + } + int count = 0; + for (int i = 0; i < human.size(); i++) { + if (human.get(i).equals(computer.get(i))) { + count++; + } + } + return count; + } +} diff --git a/src/test/java/baseball/domain/NumberGeneratorTest.java b/src/test/java/baseball/domain/number_generator/NumberGeneratorTest.java similarity index 71% rename from src/test/java/baseball/domain/NumberGeneratorTest.java rename to src/test/java/baseball/domain/number_generator/NumberGeneratorTest.java index 044e69e8..9fa788b1 100644 --- a/src/test/java/baseball/domain/NumberGeneratorTest.java +++ b/src/test/java/baseball/domain/number_generator/NumberGeneratorTest.java @@ -1,18 +1,15 @@ -package baseball.domain; +package baseball.domain.number_generator; import static org.assertj.core.api.Assertions.*; import java.util.Date; -import java.util.HashSet; import java.util.List; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; -import baseball.domain.number_generator.INumberGenerator; -import baseball.domain.number_generator.IntNumberGenerator; - public class NumberGeneratorTest { + static int generateNumberCount = 3; private final INumberGenerator> numberGenerator = new IntNumberGenerator(); @DisplayName("1에서 9까지 서로 다른 임의의 수 3개를 생성한다") @@ -22,7 +19,7 @@ void generateIntNumbers() { Date now = new Date(); // when - List generatedNumbers = numberGenerator.execute(now); + List generatedNumbers = numberGenerator.execute(now, 3); // then assertThat(generatedNumbers).hasSize(3); @@ -36,8 +33,8 @@ void generateFixedNumbers() { Date fixedDate = new Date(1000L); // 고정된 타임스탬프 사용 // when - List result1 = numberGenerator.execute(fixedDate); - List result2 = numberGenerator.execute(fixedDate); + List result1 = numberGenerator.execute(fixedDate, generateNumberCount); + List result2 = numberGenerator.execute(fixedDate, generateNumberCount); // then assertThat(result1).isEqualTo(result2); diff --git a/src/test/java/baseball/domain/score_calculator/ScoreCalculatorTest.java b/src/test/java/baseball/domain/score_calculator/ScoreCalculatorTest.java new file mode 100644 index 00000000..f13aec0b --- /dev/null +++ b/src/test/java/baseball/domain/score_calculator/ScoreCalculatorTest.java @@ -0,0 +1,61 @@ +package baseball.domain.score_calculator; + +import static org.assertj.core.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import baseball.domain.strike_zone_calculator.BallCalculator; +import baseball.domain.strike_zone_calculator.IScoreCalculator; +import baseball.domain.strike_zone_calculator.StrikeCalculator; + +public class ScoreCalculatorTest { + IScoreCalculator strikeCalculator; + IScoreCalculator ballCalculator; + + static List strikeTestCases() { + return List.of( + Arguments.of(List.of(1, 2, 3), List.of(1, 2, 3), 3), + Arguments.of(List.of(3, 2, 1), List.of(1, 2, 3), 1), + Arguments.of(List.of(3, 1, 2), List.of(1, 2, 3), 0), + Arguments.of(List.of(1, 5, 4), List.of(1, 2, 3), 1) + ); + } + + static List ballTestCases() { + return List.of( + Arguments.of(List.of(1, 2, 3), List.of(1, 2, 3), 0), + Arguments.of(List.of(3, 2, 1), List.of(1, 2, 3), 2), + Arguments.of(List.of(3, 1, 2), List.of(1, 2, 3), 3), + Arguments.of(List.of(1, 5, 4), List.of(1, 2, 3), 0) + ); + } + + @BeforeEach + void setUp() { + this.strikeCalculator = new StrikeCalculator(); + this.ballCalculator = new BallCalculator(this.strikeCalculator); + } + + @DisplayName("볼 계산기 테스트") + @ParameterizedTest + @MethodSource("ballTestCases") + void ballCalculator(List human, List computer, int expected) { + int result = ballCalculator.calculate(human, computer); + assertThat(result).isEqualTo(expected); + } + + @DisplayName("스트라이크 계산기 테스트") + @ParameterizedTest + @MethodSource("strikeTestCases") + void strikeCalculator(List human, List computer, int expected) { + + int result = strikeCalculator.calculate(human, computer); + assertThat(result).isEqualTo(expected); + } +} From 0720d9bb9d8c7340e067909e2ae646e91e45cc0b Mon Sep 17 00:00:00 2001 From: TaeYoon17 Date: Fri, 6 Feb 2026 11:03:03 +0900 Subject: [PATCH 6/8] =?UTF-8?q?feat:=201~9=20=EC=84=9C=EB=A1=9C=20?= =?UTF-8?q?=EB=8B=A4=EB=A5=B8=20=EC=9E=84=EC=9D=98=EC=9D=98=20=EC=88=98=20?= =?UTF-8?q?3=EA=B0=9C=20=EC=83=9D=EC=84=B1=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [적용 사항] * Coordinator 패턴을 도입, 게임의 전체 흐름을 관리 MainCoordinator를 구현 * PlayViewControllerDelegate와 HomeViewControllerDelegate 구현, 각 뷰 컨트롤러에서 발생하는 이벤트(게임 종료, 재시작)를 MainCoordinator가 받아 처리 * 게임 시작 및 재시작 시 PlayViewController를, 게임이 끝나면 HomeViewController를 표시하도록 화면 전환 로직을 구현했습니다. * DIContainer를 활용하여 PlayViewController 생성 시 의존성인 integerNumberGenerator를 주입하도록 구조를 개선했습니다. [적용 이유] * 뷰 컨트롤러(View Controller) 간의 직접적인 의존 관계를 제거 및 화면 흐름 제어 및 책임을 Coordinator로 위임을 통한 객체 역할 분리하기 * 화면 전환 로직을 한 곳에서 중앙 관리함으로써 코드의 유지보수성을 높이고, 향후 새로운 화면이 추가되거나 흐름이 변경될 때 유연하게 대처할 수 있는 구도 * Coordinator가 뷰 컨트롤러의 생성과 소멸을 관리 --- README.md | 25 +++++---- src/main/java/baseball/BaseBall.java | 12 +++++ src/main/java/baseball/DIContainer.java | 39 ++++++++++++++ .../baseball/coordinator/Coordinator.java | 9 ++++ .../baseball/coordinator/MainCoordinator.java | 52 +++++++++++++++++++ src/main/java/baseball/view/PlayView.java | 19 +++++++ .../common/ViewController.java | 5 ++ .../HomeViewController.java | 24 +++++++++ .../HomeViewControllerDelegate.java | 7 +++ .../PlayViewController.java | 29 +++++++++++ .../PlayViewControllerDelegate.java | 5 ++ 11 files changed, 215 insertions(+), 11 deletions(-) create mode 100644 src/main/java/baseball/BaseBall.java create mode 100644 src/main/java/baseball/DIContainer.java create mode 100644 src/main/java/baseball/coordinator/Coordinator.java create mode 100644 src/main/java/baseball/coordinator/MainCoordinator.java create mode 100644 src/main/java/baseball/view/PlayView.java create mode 100644 src/main/java/baseball/view_controller/common/ViewController.java create mode 100644 src/main/java/baseball/view_controller/home_view_controller/HomeViewController.java create mode 100644 src/main/java/baseball/view_controller/home_view_controller/HomeViewControllerDelegate.java create mode 100644 src/main/java/baseball/view_controller/play_view_controller/PlayViewController.java create mode 100644 src/main/java/baseball/view_controller/play_view_controller/PlayViewControllerDelegate.java diff --git a/README.md b/README.md index 11a1e112..3109aa7d 100644 --- a/README.md +++ b/README.md @@ -50,18 +50,21 @@ ### 핵심 로직 (Domain) 4. feat: 1~9 서로 다른 임의의 수 3개 생성 구현 (Green) ✅ -5. test: 볼, 스트라이크 비교 로직 테스트 -6. feat: 볼, 스트라이크 비교 및 판별 로직 구현 (Green) -7. refactor: 비교 로직 가독성 개선 (Refactor) +5. test: 볼, 스트라이크 비교 로직 테스트 ✅ +6. feat: 볼, 스트라이크 비교 및 판별 로직 구현 (Green) ✅ +7. refactor: 비교 로직 가독성 개선 (Refactor) ✅ -### 입출력 및 유효성 검사 (View/Validation) +### 게임 진행 (Controller) -8. test: 유저 숫자 입력 유효성 검사 (3자리 숫자, 중복 확인) -9. feat: 유저 입력 파싱 및 예외 처리 구현 (Green) -10. test: 게임 재시작/종료 입력 검증 -11. feat: 재시작(1)/종료(2) 입력 처리 구현 (Green) +8. feat: 게임 흐름 제어 구현 ✅ +9. feat: 게임 메인 플레이 로직 및 도메인 생성 +10. feat: 게인 컨트롤러 - 플레이어 로직 병합 +11. refactor: 전체 구조 리팩토링 및 메소드 분리 (Refactor) -### 게임 진행 (Controller) +### 입출력 및 유효성 검사 (View/Validation) + +12. test: 유저 숫자 입력 유효성 검사 (3자리 숫자, 중복 확인) +13. feat: 유저 입력 파싱 및 예외 처리 구현 (Green) +14. test: 게임 재시작/종료 입력 검증 +15. feat: 재시작(1)/종료(2) 입력 처리 구현 (Green) -12. feat: 게임 흐름 제어 및 메인 루프 구현 -13. refactor: 전체 구조 리팩토링 및 메소드 분리 (Refactor) diff --git a/src/main/java/baseball/BaseBall.java b/src/main/java/baseball/BaseBall.java new file mode 100644 index 00000000..d78353e8 --- /dev/null +++ b/src/main/java/baseball/BaseBall.java @@ -0,0 +1,12 @@ +package baseball; + +import baseball.coordinator.MainCoordinator; + +public class BaseBall { + + public static void main(String[] args) { + MainCoordinator mainCoordinator = new MainCoordinator(); + mainCoordinator.start(); + } +} + diff --git a/src/main/java/baseball/DIContainer.java b/src/main/java/baseball/DIContainer.java new file mode 100644 index 00000000..958d5970 --- /dev/null +++ b/src/main/java/baseball/DIContainer.java @@ -0,0 +1,39 @@ +package baseball; + +import java.util.List; + +import baseball.domain.number_generator.INumberGenerator; +import baseball.domain.number_generator.IntNumberGenerator; +import baseball.domain.strike_zone_calculator.BallCalculator; +import baseball.domain.strike_zone_calculator.IScoreCalculator; +import baseball.domain.strike_zone_calculator.StrikeCalculator; + +public class DIContainer { + public static DIContainer shared = new DIContainer(); + + public Domain domain; + public Util util; + + private DIContainer() { + this.util = new Util(); + this.domain = new Domain(util); + } + + public static class Util { + Util() { + + } + } + + public static class Domain { + public INumberGenerator> integerNumberGenerator; + public IScoreCalculator strikeCalculator; + public IScoreCalculator ballCalculator; + + Domain(Util util) { + integerNumberGenerator = new IntNumberGenerator(); + strikeCalculator = new StrikeCalculator(); + ballCalculator = new BallCalculator(strikeCalculator); + } + } +} diff --git a/src/main/java/baseball/coordinator/Coordinator.java b/src/main/java/baseball/coordinator/Coordinator.java new file mode 100644 index 00000000..7cfd502b --- /dev/null +++ b/src/main/java/baseball/coordinator/Coordinator.java @@ -0,0 +1,9 @@ +package baseball.coordinator; + +import java.util.List; + +public interface Coordinator { + List childCoordinators = List.of(); + + void start(); +} diff --git a/src/main/java/baseball/coordinator/MainCoordinator.java b/src/main/java/baseball/coordinator/MainCoordinator.java new file mode 100644 index 00000000..74978348 --- /dev/null +++ b/src/main/java/baseball/coordinator/MainCoordinator.java @@ -0,0 +1,52 @@ +package baseball.coordinator; + +import baseball.DIContainer; +import baseball.view_controller.home_view_controller.HomeViewController; +import baseball.view_controller.home_view_controller.HomeViewControllerDelegate; +import baseball.view_controller.play_view_controller.PlayViewController; +import baseball.view_controller.play_view_controller.PlayViewControllerDelegate; + +public class MainCoordinator implements Coordinator, PlayViewControllerDelegate, HomeViewControllerDelegate { + private final DIContainer diContainer = DIContainer.shared; + private HomeViewController homeViewController; + private PlayViewController playViewController; + + // --- Coordinator 구현 --- + @Override + public void start() { + showPlay(); + } + + // --- HomeViewControllerDelegate 구현 --- + @Override + public void userPlaySelected() { + showPlay(); + } + + @Override + public void userExitSelected() { + homeViewController = null; + playViewController = null; + } + + // --- PlayViewControllerDelegate 구현 --- + @Override + public void playFinished() { + showHome(); + } + + private void showHome() { + this.homeViewController = new HomeViewController(); + this.homeViewController.delegate = this; + this.homeViewController.render(); + } + + private void showPlay() { + // PlayViewController에 필요한 의존성을 여기서 생성하고 주입합니다. + this.playViewController = new PlayViewController( + diContainer.domain.integerNumberGenerator + ); + this.playViewController.delegate = this; + this.playViewController.render(); + } +} diff --git a/src/main/java/baseball/view/PlayView.java b/src/main/java/baseball/view/PlayView.java new file mode 100644 index 00000000..f385ca4f --- /dev/null +++ b/src/main/java/baseball/view/PlayView.java @@ -0,0 +1,19 @@ +package baseball.view; + +import java.util.List; +import java.util.Scanner; + +public class PlayView { + + List input() { + Scanner sc = new Scanner(System.in); + System.out.print("숫자를 입력해주세요 : "); + String userRawNumber = sc.next(); + sc.close(); + for (int i = 0; i < userRawNumber.length(); i += 1) { + + } + return List.of(1, 2, 3); + } +} + diff --git a/src/main/java/baseball/view_controller/common/ViewController.java b/src/main/java/baseball/view_controller/common/ViewController.java new file mode 100644 index 00000000..a3127646 --- /dev/null +++ b/src/main/java/baseball/view_controller/common/ViewController.java @@ -0,0 +1,5 @@ +package baseball.view_controller.common; + +public interface ViewController { + void render(); +} diff --git a/src/main/java/baseball/view_controller/home_view_controller/HomeViewController.java b/src/main/java/baseball/view_controller/home_view_controller/HomeViewController.java new file mode 100644 index 00000000..184b6410 --- /dev/null +++ b/src/main/java/baseball/view_controller/home_view_controller/HomeViewController.java @@ -0,0 +1,24 @@ +package baseball.view_controller.home_view_controller; + +import java.util.Scanner; + +import baseball.view_controller.common.ViewController; + +public class HomeViewController implements ViewController { + private final Scanner sc = new Scanner(System.in); + public HomeViewControllerDelegate delegate; + + @Override + public void render() { + System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."); + int userMode = sc.nextInt(); + System.out.println("홈화면 선택" + userMode); + if (userMode == 1) { + delegate.userPlaySelected(); + return; + } + System.out.println("게임을 종료하였습니다 바이 바이!!"); + delegate.userExitSelected(); + } +} + diff --git a/src/main/java/baseball/view_controller/home_view_controller/HomeViewControllerDelegate.java b/src/main/java/baseball/view_controller/home_view_controller/HomeViewControllerDelegate.java new file mode 100644 index 00000000..3636fcb4 --- /dev/null +++ b/src/main/java/baseball/view_controller/home_view_controller/HomeViewControllerDelegate.java @@ -0,0 +1,7 @@ +package baseball.view_controller.home_view_controller; + +public interface HomeViewControllerDelegate { + void userExitSelected(); + + void userPlaySelected(); +} diff --git a/src/main/java/baseball/view_controller/play_view_controller/PlayViewController.java b/src/main/java/baseball/view_controller/play_view_controller/PlayViewController.java new file mode 100644 index 00000000..0b9c9a8c --- /dev/null +++ b/src/main/java/baseball/view_controller/play_view_controller/PlayViewController.java @@ -0,0 +1,29 @@ +package baseball.view_controller.play_view_controller; + +import java.util.List; +import java.util.Scanner; + +import baseball.domain.number_generator.INumberGenerator; +import baseball.view_controller.common.ViewController; + +public class PlayViewController implements ViewController { + private final INumberGenerator> numberGenerator; + public PlayViewControllerDelegate delegate; + + public PlayViewController( + INumberGenerator> numberGenerator + ) { + this.numberGenerator = numberGenerator; + } + + @Override + public void render() { + while (true) { + System.out.println("아무 키나 누르세요"); + Scanner sc = new Scanner(System.in); + String input = sc.next(); + break; + } + delegate.playFinished(); + } +} diff --git a/src/main/java/baseball/view_controller/play_view_controller/PlayViewControllerDelegate.java b/src/main/java/baseball/view_controller/play_view_controller/PlayViewControllerDelegate.java new file mode 100644 index 00000000..89d2fe2a --- /dev/null +++ b/src/main/java/baseball/view_controller/play_view_controller/PlayViewControllerDelegate.java @@ -0,0 +1,5 @@ +package baseball.view_controller.play_view_controller; + +public interface PlayViewControllerDelegate { + void playFinished(); +} From 1476c62f4343c297b669913e3960b7e9d63ca3c0 Mon Sep 17 00:00:00 2001 From: TaeYoon17 Date: Fri, 6 Feb 2026 18:21:17 +0900 Subject: [PATCH 7/8] =?UTF-8?q?feat:=20=EA=B2=8C=EC=9E=84=20=EB=A9=94?= =?UTF-8?q?=EC=9D=B8=20=ED=94=8C=EB=A0=88=EC=9D=B4=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EB=B0=8F=20=EB=8F=84=EB=A9=94=EC=9D=B8=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=E2=9C=85?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [적용 사항] - 도메인 객체 생성: 엔티티를 도입하여 스트라이크/볼 카운트 및 게임 결과를 객체로 표현했습니다. - 스트라이크 존 계산 로직 구현: 인터페이스와 구현체를 추가하여 사용자 입력과 컴퓨터 숫자를 비교하고 스트라이크, 볼, 낫싱, 게임 종료 등의 결과 로직 분리 - 메인 플레이 로직 통합: 에 를 주입받아 게임의 메인 플레이 로직을 구현했습니다. 사용자 입력 처리, 스트라이크 존 계산, 결과 출력, 게임 종료 조건 등을 포함합니다. - 의존성 주입 컨테이너 업데이트 - 테스트 코드 추가: 스트라이크 존 계산 로직의 정확성을 검증 [적용 이유] - 핵심 게임 로직 분리 및 모듈화: 스트라이크 존 계산이라는 핵심 로직을 별도의 모듈()로 분리하여 코드의 응집도를 높이고 유지보수성을 향상시켰습니다. - 객체 지향적 설계 강화: 와 를 통해 게임의 상태와 결과를 명확한 객체로 관리하여 코드의 가독성과 확장성을 높였습니다. - 의존성 관리 개선: 를 활용하여 객체 간의 의존성을 명확히 하고, 가 직접 의존성을 생성하지 않고 주입받도록 하여 테스트 용이성을 확보했습니다. - 테스트 용이성 확보: 각 도메인 로직을 독립적인 단위로 테스트할 수 있도록 하여, 기능 변경 시 발생할 수 있는 잠재적 버그를 줄이고 코드의 안정성을 확보했습니다. --- README.md | 2 +- src/main/java/baseball/DIContainer.java | 10 ++- .../baseball/coordinator/MainCoordinator.java | 3 +- .../domain/entity/BallCountEntity.java | 34 ++++++++++ .../domain/entity/StrikeZoneResult.java | 67 +++++++++++++++++++ .../BallCalculator.java | 2 +- .../IScoreCalculator.java | 2 +- .../StrikeCalculator.java | 2 +- .../IStrikeZoneCalculator.java | 13 ++++ .../StrikeZoneCalculator.java | 38 +++++++++++ .../PlayViewController.java | 58 ++++++++++++++-- .../score_calculator/ScoreCalculatorTest.java | 4 -- .../StrikeZoneCalculatorTest.java | 54 +++++++++++++++ 13 files changed, 272 insertions(+), 17 deletions(-) create mode 100644 src/main/java/baseball/domain/entity/BallCountEntity.java create mode 100644 src/main/java/baseball/domain/entity/StrikeZoneResult.java rename src/main/java/baseball/domain/{strike_zone_calculator => score_calculator}/BallCalculator.java (93%) rename src/main/java/baseball/domain/{strike_zone_calculator => score_calculator}/IScoreCalculator.java (72%) rename src/main/java/baseball/domain/{strike_zone_calculator => score_calculator}/StrikeCalculator.java (90%) create mode 100644 src/main/java/baseball/domain/strike_zone_calculator/IStrikeZoneCalculator.java create mode 100644 src/main/java/baseball/domain/strike_zone_calculator/StrikeZoneCalculator.java create mode 100644 src/test/java/baseball/domain/strike_zone_calculator/StrikeZoneCalculatorTest.java diff --git a/README.md b/README.md index 3109aa7d..4661ddd0 100644 --- a/README.md +++ b/README.md @@ -57,7 +57,7 @@ ### 게임 진행 (Controller) 8. feat: 게임 흐름 제어 구현 ✅ -9. feat: 게임 메인 플레이 로직 및 도메인 생성 +9. feat: 게임 메인 플레이 로직 및 도메인 생성 ✅ 10. feat: 게인 컨트롤러 - 플레이어 로직 병합 11. refactor: 전체 구조 리팩토링 및 메소드 분리 (Refactor) diff --git a/src/main/java/baseball/DIContainer.java b/src/main/java/baseball/DIContainer.java index 958d5970..6ba2d9f8 100644 --- a/src/main/java/baseball/DIContainer.java +++ b/src/main/java/baseball/DIContainer.java @@ -4,9 +4,11 @@ import baseball.domain.number_generator.INumberGenerator; import baseball.domain.number_generator.IntNumberGenerator; -import baseball.domain.strike_zone_calculator.BallCalculator; -import baseball.domain.strike_zone_calculator.IScoreCalculator; -import baseball.domain.strike_zone_calculator.StrikeCalculator; +import baseball.domain.score_calculator.BallCalculator; +import baseball.domain.score_calculator.IScoreCalculator; +import baseball.domain.score_calculator.StrikeCalculator; +import baseball.domain.strike_zone_calculator.IStrikeZoneCalculator; +import baseball.domain.strike_zone_calculator.StrikeZoneCalculator; public class DIContainer { public static DIContainer shared = new DIContainer(); @@ -27,6 +29,7 @@ public static class Util { public static class Domain { public INumberGenerator> integerNumberGenerator; + public IStrikeZoneCalculator strikeZoneCalculator; public IScoreCalculator strikeCalculator; public IScoreCalculator ballCalculator; @@ -34,6 +37,7 @@ public static class Domain { integerNumberGenerator = new IntNumberGenerator(); strikeCalculator = new StrikeCalculator(); ballCalculator = new BallCalculator(strikeCalculator); + strikeZoneCalculator = new StrikeZoneCalculator(this.strikeCalculator, this.ballCalculator); } } } diff --git a/src/main/java/baseball/coordinator/MainCoordinator.java b/src/main/java/baseball/coordinator/MainCoordinator.java index 74978348..4680c3fe 100644 --- a/src/main/java/baseball/coordinator/MainCoordinator.java +++ b/src/main/java/baseball/coordinator/MainCoordinator.java @@ -44,7 +44,8 @@ private void showHome() { private void showPlay() { // PlayViewController에 필요한 의존성을 여기서 생성하고 주입합니다. this.playViewController = new PlayViewController( - diContainer.domain.integerNumberGenerator + diContainer.domain.integerNumberGenerator, + diContainer.domain.strikeZoneCalculator ); this.playViewController.delegate = this; this.playViewController.render(); diff --git a/src/main/java/baseball/domain/entity/BallCountEntity.java b/src/main/java/baseball/domain/entity/BallCountEntity.java new file mode 100644 index 00000000..70e6a8ce --- /dev/null +++ b/src/main/java/baseball/domain/entity/BallCountEntity.java @@ -0,0 +1,34 @@ +package baseball.domain.entity; + +import java.util.Objects; + +public class BallCountEntity { + public int ball; + public int strike; + + public BallCountEntity(int strikeCount, int ballCount) { + this.strike = strikeCount; + this.ball = ballCount; + } + + @Override + public boolean equals(Object o) { + // 1. 자기 자신과 비교하면 true + if (this == o) + return true; + + // 2. null이거나 클래스 종류가 다르면 false + if (o == null || getClass() != o.getClass()) + return false; + + // 3. 값을 비교 (strike와 ball이 모두 같아야 true) + BallCountEntity that = (BallCountEntity)o; + return ball == that.ball && strike == that.strike; + } + + @Override + public int hashCode() { + // 객체의 값을 기반으로 고유 번호 생성 (HashMap 등에서 필수) + return Objects.hash(ball, strike); + } +} diff --git a/src/main/java/baseball/domain/entity/StrikeZoneResult.java b/src/main/java/baseball/domain/entity/StrikeZoneResult.java new file mode 100644 index 00000000..a119d446 --- /dev/null +++ b/src/main/java/baseball/domain/entity/StrikeZoneResult.java @@ -0,0 +1,67 @@ +package baseball.domain.entity; + +import java.util.Objects; + +public class StrikeZoneResult { + private final Type type; + private final BallCountEntity ballCount; + + // 생성자들을 private으로 막고 Factory 메서드 사용 + private StrikeZoneResult(Type type, BallCountEntity ballCount) { + this.type = type; + this.ballCount = ballCount; + } + + // Factory 메서드들 (Swift의 case 생성자 역할) + public static StrikeZoneResult nothing() { + return new StrikeZoneResult(Type.NOTHING, new BallCountEntity(0, 0)); + } + + public static StrikeZoneResult playing(BallCountEntity ballCount) { + return new StrikeZoneResult(Type.PLAYING, ballCount); + } + + public static StrikeZoneResult complete(BallCountEntity ballCount) { + return new StrikeZoneResult(Type.COMPLETE, ballCount); + } + + // Getter + public Type getType() { + return type; + } + + public BallCountEntity get() { + return this.ballCount; + } + + public int getBall() { + return this.ballCount.ball; + } + + public int getStrike() { + return this.ballCount.strike; + } + + @Override + public boolean equals(Object o) { + // 1. 주소값이 같으면 같은 객체 + if (this == o) + return true; + // 2. null이거나 클래스 타입이 다르면 다른 객체 + if (o == null || getClass() != o.getClass()) + return false; + + // 3. 타입 캐스팅 후 필드값 비교 + StrikeZoneResult that = (StrikeZoneResult)o; + return type == that.type && Objects.equals(ballCount, that.ballCount); + } + + @Override + public int hashCode() { + // equals에 사용된 필드들로 해시코드 생성 + return Objects.hash(type, ballCount); + } + + // 내부 Enum 정의 (종류 구분용) + public enum Type {NOTHING, PLAYING, COMPLETE} +} diff --git a/src/main/java/baseball/domain/strike_zone_calculator/BallCalculator.java b/src/main/java/baseball/domain/score_calculator/BallCalculator.java similarity index 93% rename from src/main/java/baseball/domain/strike_zone_calculator/BallCalculator.java rename to src/main/java/baseball/domain/score_calculator/BallCalculator.java index 8c1f18e9..94238030 100644 --- a/src/main/java/baseball/domain/strike_zone_calculator/BallCalculator.java +++ b/src/main/java/baseball/domain/score_calculator/BallCalculator.java @@ -1,4 +1,4 @@ -package baseball.domain.strike_zone_calculator; +package baseball.domain.score_calculator; import java.util.HashSet; import java.util.List; diff --git a/src/main/java/baseball/domain/strike_zone_calculator/IScoreCalculator.java b/src/main/java/baseball/domain/score_calculator/IScoreCalculator.java similarity index 72% rename from src/main/java/baseball/domain/strike_zone_calculator/IScoreCalculator.java rename to src/main/java/baseball/domain/score_calculator/IScoreCalculator.java index d1e6addf..1e7a9b1c 100644 --- a/src/main/java/baseball/domain/strike_zone_calculator/IScoreCalculator.java +++ b/src/main/java/baseball/domain/score_calculator/IScoreCalculator.java @@ -1,4 +1,4 @@ -package baseball.domain.strike_zone_calculator; +package baseball.domain.score_calculator; import java.util.List; diff --git a/src/main/java/baseball/domain/strike_zone_calculator/StrikeCalculator.java b/src/main/java/baseball/domain/score_calculator/StrikeCalculator.java similarity index 90% rename from src/main/java/baseball/domain/strike_zone_calculator/StrikeCalculator.java rename to src/main/java/baseball/domain/score_calculator/StrikeCalculator.java index a14b69c0..1424c333 100644 --- a/src/main/java/baseball/domain/strike_zone_calculator/StrikeCalculator.java +++ b/src/main/java/baseball/domain/score_calculator/StrikeCalculator.java @@ -1,4 +1,4 @@ -package baseball.domain.strike_zone_calculator; +package baseball.domain.score_calculator; import java.util.List; diff --git a/src/main/java/baseball/domain/strike_zone_calculator/IStrikeZoneCalculator.java b/src/main/java/baseball/domain/strike_zone_calculator/IStrikeZoneCalculator.java new file mode 100644 index 00000000..2dd457dd --- /dev/null +++ b/src/main/java/baseball/domain/strike_zone_calculator/IStrikeZoneCalculator.java @@ -0,0 +1,13 @@ +package baseball.domain.strike_zone_calculator; + +import java.util.List; + +import baseball.domain.entity.StrikeZoneResult; +import baseball.domain.score_calculator.IScoreCalculator; + +public interface IStrikeZoneCalculator { + IScoreCalculator strikeCalculator = null; + IScoreCalculator ballCalculator = null; + + StrikeZoneResult execute(List human, List computer); +} diff --git a/src/main/java/baseball/domain/strike_zone_calculator/StrikeZoneCalculator.java b/src/main/java/baseball/domain/strike_zone_calculator/StrikeZoneCalculator.java new file mode 100644 index 00000000..48362d96 --- /dev/null +++ b/src/main/java/baseball/domain/strike_zone_calculator/StrikeZoneCalculator.java @@ -0,0 +1,38 @@ +package baseball.domain.strike_zone_calculator; + +import java.util.List; + +import baseball.domain.entity.BallCountEntity; +import baseball.domain.entity.StrikeZoneResult; +import baseball.domain.score_calculator.IScoreCalculator; + +/// Strike Zone Ball Count를 연산하는 객체 +public class StrikeZoneCalculator implements IStrikeZoneCalculator { + private final IScoreCalculator strikeCalculator; + private final IScoreCalculator ballCalculator; + + public StrikeZoneCalculator( + IScoreCalculator strikeCalculator, + IScoreCalculator ballCalculator + ) { + this.strikeCalculator = strikeCalculator; + this.ballCalculator = ballCalculator; + } + + @Override + public StrikeZoneResult execute(List human, List computer) { + if (human.size() != computer.size()) { + throw new IllegalStateException("각 답의 개수가 맞지 않습니다!"); + } + + int strikeCount = strikeCalculator.calculate(human, computer); + int ballCount = ballCalculator.calculate(human, computer); + BallCountEntity entity = new BallCountEntity(strikeCount, ballCount); + + if (strikeCount == computer.size()) + return StrikeZoneResult.complete(entity); + if (entity.strike == 0 && entity.ball == 0) + return StrikeZoneResult.nothing(); + return StrikeZoneResult.playing(entity); + } +} diff --git a/src/main/java/baseball/view_controller/play_view_controller/PlayViewController.java b/src/main/java/baseball/view_controller/play_view_controller/PlayViewController.java index 0b9c9a8c..1764e063 100644 --- a/src/main/java/baseball/view_controller/play_view_controller/PlayViewController.java +++ b/src/main/java/baseball/view_controller/play_view_controller/PlayViewController.java @@ -1,29 +1,77 @@ package baseball.view_controller.play_view_controller; +import java.util.ArrayList; +import java.util.Date; import java.util.List; import java.util.Scanner; +import baseball.domain.entity.StrikeZoneResult; import baseball.domain.number_generator.INumberGenerator; +import baseball.domain.strike_zone_calculator.IStrikeZoneCalculator; import baseball.view_controller.common.ViewController; public class PlayViewController implements ViewController { private final INumberGenerator> numberGenerator; + private final IStrikeZoneCalculator strikeZoneCalculator; public PlayViewControllerDelegate delegate; public PlayViewController( - INumberGenerator> numberGenerator + INumberGenerator> numberGenerator, + IStrikeZoneCalculator strikeZoneCalculator ) { this.numberGenerator = numberGenerator; + this.strikeZoneCalculator = strikeZoneCalculator; } @Override public void render() { + List computerNumber = numberGenerator.execute(new Date(), 3); while (true) { - System.out.println("아무 키나 누르세요"); - Scanner sc = new Scanner(System.in); - String input = sc.next(); - break; + List userNumber = tempInputParser(); + StrikeZoneResult result = strikeZoneCalculator.execute(userNumber, computerNumber); + var output = tempOutputRouter(result); + System.out.print(output); + if (result.getType() == StrikeZoneResult.Type.COMPLETE) { + System.out.println("게임 끝!"); + break; + } } delegate.playFinished(); } + + private List tempInputParser() { + System.out.print("숫자를 입력해주세요: "); + Scanner sc = new Scanner(System.in); + String input = sc.next(); + + List numbers = new ArrayList<>(); + + // 문자열을 문자 배열로 변환하여 반복 + for (char c : input.toCharArray()) { + // Character.getNumericValue(c)는 문자 '3'을 숫자 3으로 바꿔줍니다. + // (단순 형변환 (int)c를 하면 아스키코드 값 51이 나오므로 주의!) + numbers.add(Character.getNumericValue(c)); + } + + return numbers; + } + + private String tempOutputRouter(StrikeZoneResult result) { + StrikeZoneResult.Type resultType = result.getType(); + if (resultType == StrikeZoneResult.Type.COMPLETE) { + return result.getStrike() + "개의 숫자를 모두 맞히셨습니다! "; + } + if (resultType == StrikeZoneResult.Type.NOTHING) { + return "낫싱\n"; + } + int ballCnt = result.getBall(); + int strikeCnt = result.getStrike(); + List resList = new ArrayList<>(); + + if (strikeCnt > 0) + resList.add(strikeCnt + "스트라이크"); + if (ballCnt > 0) + resList.add(ballCnt + "볼"); + return String.join(" ", resList) + "\n"; + } } diff --git a/src/test/java/baseball/domain/score_calculator/ScoreCalculatorTest.java b/src/test/java/baseball/domain/score_calculator/ScoreCalculatorTest.java index f13aec0b..93a09302 100644 --- a/src/test/java/baseball/domain/score_calculator/ScoreCalculatorTest.java +++ b/src/test/java/baseball/domain/score_calculator/ScoreCalculatorTest.java @@ -10,10 +10,6 @@ import org.junit.jupiter.params.provider.Arguments; import org.junit.jupiter.params.provider.MethodSource; -import baseball.domain.strike_zone_calculator.BallCalculator; -import baseball.domain.strike_zone_calculator.IScoreCalculator; -import baseball.domain.strike_zone_calculator.StrikeCalculator; - public class ScoreCalculatorTest { IScoreCalculator strikeCalculator; IScoreCalculator ballCalculator; diff --git a/src/test/java/baseball/domain/strike_zone_calculator/StrikeZoneCalculatorTest.java b/src/test/java/baseball/domain/strike_zone_calculator/StrikeZoneCalculatorTest.java new file mode 100644 index 00000000..ecb64885 --- /dev/null +++ b/src/test/java/baseball/domain/strike_zone_calculator/StrikeZoneCalculatorTest.java @@ -0,0 +1,54 @@ +package baseball.domain.strike_zone_calculator; + +import static org.assertj.core.api.Assertions.*; + +import java.util.List; + +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.params.ParameterizedTest; +import org.junit.jupiter.params.provider.Arguments; +import org.junit.jupiter.params.provider.MethodSource; + +import baseball.domain.entity.BallCountEntity; +import baseball.domain.entity.StrikeZoneResult; +import baseball.domain.score_calculator.BallCalculator; +import baseball.domain.score_calculator.IScoreCalculator; +import baseball.domain.score_calculator.StrikeCalculator; + +public class StrikeZoneCalculatorTest { + + IScoreCalculator strikeCalculator; + IScoreCalculator ballCalculator; + + IStrikeZoneCalculator strikeZoneCalculator; + + static List strikeZoneTestCases() { + return List.of( + Arguments.of(List.of(1, 2, 3), List.of(1, 2, 3), StrikeZoneResult.complete(new BallCountEntity(3, 0))), + Arguments.of(List.of(3, 2, 1), List.of(1, 2, 3), StrikeZoneResult.playing(new BallCountEntity(1, 2))), + Arguments.of(List.of(3, 1, 2), List.of(1, 2, 3), StrikeZoneResult.playing(new BallCountEntity(0, 3))), + Arguments.of(List.of(1, 5, 4), List.of(1, 2, 3), StrikeZoneResult.playing(new BallCountEntity(1, 0))), + Arguments.of(List.of(7, 5, 4), List.of(1, 2, 3), StrikeZoneResult.nothing()) + ); + } + + @BeforeEach + void setUp() { + this.strikeCalculator = new StrikeCalculator(); + this.ballCalculator = new BallCalculator(this.strikeCalculator); + + } + + @DisplayName("스트라이크 존 결과 테스트") + @ParameterizedTest + @MethodSource("strikeZoneTestCases") + void strikeZoneResult(List human, List computer, StrikeZoneResult expected) { + + var sut = new StrikeZoneCalculator(strikeCalculator, ballCalculator); + + var result = sut.execute(human, computer); + + assertThat(result).isEqualTo(expected); + } +} From 0bf0ec6798d2c2a8ff994478055421ed2e2b1e73 Mon Sep 17 00:00:00 2001 From: TaeYoon17 Date: Sun, 8 Feb 2026 02:18:23 +0900 Subject: [PATCH 8/8] =?UTF-8?q?refactor:=20=EC=A0=84=EC=B2=B4=20=EA=B5=AC?= =?UTF-8?q?=EC=A1=B0=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=20=EB=B0=8F=20?= =?UTF-8?q?=EB=B7=B0=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EA=B0=84=20?= =?UTF-8?q?=EB=A9=94=EC=84=9C=EB=93=9C=20=EB=B6=84=EB=A6=AC?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit [적용 사항] - 유저 플레이 모드 엔티티 추가: 정확히 재생 중인지, 끝낼 것인지 나타내는 모드 열거형을 엔티티로 추가합니다. - DIContainer > generatedNumber 값 추가: 몇 개의 숫자를 기반으로 게임을 할 지 결정합니다. - HomeView, PlayView와 ViewController 분리 [적용 이유] - 1(시작), 2(종료)와 같은 매직 넘버를 사용하는 대신, UserPlayMode.Start, UserPlayMode.Finish와 같이 의미가 명확한 열거형(Enum)을 사용했습니다. 이를 통해 코드의 가독성이 향상되고, 컴파일 시점에 타입을 검증할 수 있어 잘못된 값이 입력될 가능성을 원천적으로 차단합니다. - '화면에 보여주는 책임(View)'과 '데이터를 해석하고 흐름을 제어하는 책임(ViewController)'을 분리, 향후 UI를 콘솔에서 GUI로 변경 대응을 위함 --- README.md | 11 ++-- src/main/java/baseball/DIContainer.java | 14 ++-- .../baseball/coordinator/MainCoordinator.java | 1 + .../baseball/domain/entity/UserPlayMode.java | 3 + src/main/java/baseball/view/HomeView.java | 38 +++++++++++ src/main/java/baseball/view/PlayView.java | 59 ++++++++++++++--- .../HomeViewController.java | 29 +++++---- .../PlayViewController.java | 65 ++++++------------- 8 files changed, 143 insertions(+), 77 deletions(-) create mode 100644 src/main/java/baseball/domain/entity/UserPlayMode.java create mode 100644 src/main/java/baseball/view/HomeView.java diff --git a/README.md b/README.md index 4661ddd0..fbe9496e 100644 --- a/README.md +++ b/README.md @@ -58,13 +58,12 @@ 8. feat: 게임 흐름 제어 구현 ✅ 9. feat: 게임 메인 플레이 로직 및 도메인 생성 ✅ -10. feat: 게인 컨트롤러 - 플레이어 로직 병합 -11. refactor: 전체 구조 리팩토링 및 메소드 분리 (Refactor) +11. refactor: 전체 구조 리팩토링 및 뷰 컨트롤러 - 뷰 간 메소드 분리 (Refactor) ### 입출력 및 유효성 검사 (View/Validation) -12. test: 유저 숫자 입력 유효성 검사 (3자리 숫자, 중복 확인) -13. feat: 유저 입력 파싱 및 예외 처리 구현 (Green) -14. test: 게임 재시작/종료 입력 검증 -15. feat: 재시작(1)/종료(2) 입력 처리 구현 (Green) +12. test: 유저 숫자 입력 유효성 검사 (3자리 숫자, 중복 확인) ✅ +13. feat: 유저 입력 파싱 및 예외 처리 구현 (Green) ✅ +14. test: 게임 재시작/종료 입력 검증 ✅ +15. feat: 재시작(1)/종료(2) 입력 처리 구현 (Green) ✅ diff --git a/src/main/java/baseball/DIContainer.java b/src/main/java/baseball/DIContainer.java index 6ba2d9f8..bc201b49 100644 --- a/src/main/java/baseball/DIContainer.java +++ b/src/main/java/baseball/DIContainer.java @@ -13,8 +13,8 @@ public class DIContainer { public static DIContainer shared = new DIContainer(); - public Domain domain; - public Util util; + public final Domain domain; + public final Util util; private DIContainer() { this.util = new Util(); @@ -22,16 +22,18 @@ private DIContainer() { } public static class Util { + final public int generateNumber = 3; + Util() { } } public static class Domain { - public INumberGenerator> integerNumberGenerator; - public IStrikeZoneCalculator strikeZoneCalculator; - public IScoreCalculator strikeCalculator; - public IScoreCalculator ballCalculator; + final public INumberGenerator> integerNumberGenerator; + final public IStrikeZoneCalculator strikeZoneCalculator; + final public IScoreCalculator strikeCalculator; + final public IScoreCalculator ballCalculator; Domain(Util util) { integerNumberGenerator = new IntNumberGenerator(); diff --git a/src/main/java/baseball/coordinator/MainCoordinator.java b/src/main/java/baseball/coordinator/MainCoordinator.java index 4680c3fe..6cdc4fdf 100644 --- a/src/main/java/baseball/coordinator/MainCoordinator.java +++ b/src/main/java/baseball/coordinator/MainCoordinator.java @@ -44,6 +44,7 @@ private void showHome() { private void showPlay() { // PlayViewController에 필요한 의존성을 여기서 생성하고 주입합니다. this.playViewController = new PlayViewController( + diContainer.util.generateNumber, diContainer.domain.integerNumberGenerator, diContainer.domain.strikeZoneCalculator ); diff --git a/src/main/java/baseball/domain/entity/UserPlayMode.java b/src/main/java/baseball/domain/entity/UserPlayMode.java new file mode 100644 index 00000000..567cb95e --- /dev/null +++ b/src/main/java/baseball/domain/entity/UserPlayMode.java @@ -0,0 +1,3 @@ +package baseball.domain.entity; + +public enum UserPlayMode {Start, Finish} diff --git a/src/main/java/baseball/view/HomeView.java b/src/main/java/baseball/view/HomeView.java new file mode 100644 index 00000000..0904ef3b --- /dev/null +++ b/src/main/java/baseball/view/HomeView.java @@ -0,0 +1,38 @@ +package baseball.view; + +import java.util.Scanner; + +import baseball.domain.entity.UserPlayMode; + +public class HomeView { + private final Scanner scanner = new Scanner(System.in); + + public void askForGameOption() { + System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."); + } + + public UserPlayMode readUserPlayGame() { + String input = scanner.nextLine().trim(); // 앞뒤 공백 제거 및 줄 단위 읽기 + try { + int userMode = Integer.parseInt(input); + + if (userMode == 1) { + return UserPlayMode.Start; + } + if (userMode == 2) { + return UserPlayMode.Finish; + } + throw new IllegalArgumentException("1 또는 2만 입력 가능합니다.."); + } catch (NumberFormatException e) { + throw new IllegalArgumentException("숫자가 아닌 잘못된 형식을 입력하셨습니다."); + } + } + + public void showErrorMessage(String message) { + System.out.println("[ERROR] " + message); + } + + public void showUserEndGameMessage() { + System.out.println("게임을 종료하였습니다 바이 바이!!"); + } +} diff --git a/src/main/java/baseball/view/PlayView.java b/src/main/java/baseball/view/PlayView.java index f385ca4f..3e01e746 100644 --- a/src/main/java/baseball/view/PlayView.java +++ b/src/main/java/baseball/view/PlayView.java @@ -1,19 +1,62 @@ package baseball.view; +import java.util.ArrayList; import java.util.List; import java.util.Scanner; +import baseball.domain.entity.StrikeZoneResult; + public class PlayView { + private final Scanner scanner = new Scanner(System.in); + + public List readPlayerNumber() { + System.out.print("숫자를 입력해주세요: "); + String input = scanner.next(); + + List numbers = new ArrayList<>(); + + for (char c : input.toCharArray()) { + if (!Character.isDigit(c)) { + throw new IllegalArgumentException("숫자만 입력해야 합니다."); + } + int value = Character.getNumericValue(c); + if (numbers.contains(value)) { + throw new IllegalArgumentException("중복된 숫자가 있습니다."); + } + numbers.add(value); + } + return numbers; + } + + public void showErrorMessage(String message) { + System.out.println("[ERROR] " + message); + } + + public void showUserMatchMessage(StrikeZoneResult result) { + System.out.print(userMatchRouter(result)); + } - List input() { - Scanner sc = new Scanner(System.in); - System.out.print("숫자를 입력해주세요 : "); - String userRawNumber = sc.next(); - sc.close(); - for (int i = 0; i < userRawNumber.length(); i += 1) { - + public void showGameCompletedMessage() { + System.out.println("게임 끝!"); + } + + private String userMatchRouter(StrikeZoneResult result) { + StrikeZoneResult.Type resultType = result.getType(); + if (resultType == StrikeZoneResult.Type.COMPLETE) { + return result.getStrike() + "개의 숫자를 모두 맞히셨습니다! "; + } + if (resultType == StrikeZoneResult.Type.NOTHING) { + return "낫싱\n"; } - return List.of(1, 2, 3); + int ballCnt = result.getBall(); + int strikeCnt = result.getStrike(); + List resList = new ArrayList<>(); + + if (strikeCnt > 0) + resList.add(strikeCnt + "스트라이크"); + if (ballCnt > 0) + resList.add(ballCnt + "볼"); + return String.join(" ", resList) + "\n"; } } diff --git a/src/main/java/baseball/view_controller/home_view_controller/HomeViewController.java b/src/main/java/baseball/view_controller/home_view_controller/HomeViewController.java index 184b6410..b4982c4c 100644 --- a/src/main/java/baseball/view_controller/home_view_controller/HomeViewController.java +++ b/src/main/java/baseball/view_controller/home_view_controller/HomeViewController.java @@ -1,24 +1,31 @@ package baseball.view_controller.home_view_controller; -import java.util.Scanner; - +import baseball.domain.entity.UserPlayMode; +import baseball.view.HomeView; import baseball.view_controller.common.ViewController; public class HomeViewController implements ViewController { - private final Scanner sc = new Scanner(System.in); + private final HomeView homeView = new HomeView(); public HomeViewControllerDelegate delegate; @Override public void render() { - System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."); - int userMode = sc.nextInt(); - System.out.println("홈화면 선택" + userMode); - if (userMode == 1) { - delegate.userPlaySelected(); - return; + try { + UserPlayMode mode = homeView.readUserPlayGame(); + if (mode == UserPlayMode.Start) { + delegate.userPlaySelected(); + return; + } + + if (mode == UserPlayMode.Finish) { + homeView.showUserEndGameMessage(); + delegate.userExitSelected(); + } + } catch (IllegalArgumentException e) { + homeView.showErrorMessage(e.getMessage()); + render(); } - System.out.println("게임을 종료하였습니다 바이 바이!!"); - delegate.userExitSelected(); } } + diff --git a/src/main/java/baseball/view_controller/play_view_controller/PlayViewController.java b/src/main/java/baseball/view_controller/play_view_controller/PlayViewController.java index 1764e063..a1d69b15 100644 --- a/src/main/java/baseball/view_controller/play_view_controller/PlayViewController.java +++ b/src/main/java/baseball/view_controller/play_view_controller/PlayViewController.java @@ -1,77 +1,50 @@ package baseball.view_controller.play_view_controller; -import java.util.ArrayList; import java.util.Date; import java.util.List; -import java.util.Scanner; import baseball.domain.entity.StrikeZoneResult; import baseball.domain.number_generator.INumberGenerator; import baseball.domain.strike_zone_calculator.IStrikeZoneCalculator; +import baseball.view.PlayView; import baseball.view_controller.common.ViewController; public class PlayViewController implements ViewController { private final INumberGenerator> numberGenerator; private final IStrikeZoneCalculator strikeZoneCalculator; + private final PlayView playView = new PlayView(); + private final int generateNumber; public PlayViewControllerDelegate delegate; public PlayViewController( + int generateNumber, INumberGenerator> numberGenerator, IStrikeZoneCalculator strikeZoneCalculator ) { + this.generateNumber = generateNumber; this.numberGenerator = numberGenerator; this.strikeZoneCalculator = strikeZoneCalculator; } @Override public void render() { - List computerNumber = numberGenerator.execute(new Date(), 3); + List computerNumber = numberGenerator.execute(new Date(), generateNumber); while (true) { - List userNumber = tempInputParser(); - StrikeZoneResult result = strikeZoneCalculator.execute(userNumber, computerNumber); - var output = tempOutputRouter(result); - System.out.print(output); - if (result.getType() == StrikeZoneResult.Type.COMPLETE) { - System.out.println("게임 끝!"); - break; + try { + List userNumber = playView.readPlayerNumber(); + if (userNumber.size() != generateNumber) { + throw new IllegalArgumentException("유저 입력 형식이 맞지 않습니다!!"); + } + StrikeZoneResult result = strikeZoneCalculator.execute(userNumber, computerNumber); + playView.showUserMatchMessage(result); + if (result.getType() == StrikeZoneResult.Type.COMPLETE) { + playView.showGameCompletedMessage(); + break; + } + } catch (IllegalArgumentException e) { // ( ) 괄호 추가! + playView.showErrorMessage(e.getMessage()); } } delegate.playFinished(); } - - private List tempInputParser() { - System.out.print("숫자를 입력해주세요: "); - Scanner sc = new Scanner(System.in); - String input = sc.next(); - - List numbers = new ArrayList<>(); - - // 문자열을 문자 배열로 변환하여 반복 - for (char c : input.toCharArray()) { - // Character.getNumericValue(c)는 문자 '3'을 숫자 3으로 바꿔줍니다. - // (단순 형변환 (int)c를 하면 아스키코드 값 51이 나오므로 주의!) - numbers.add(Character.getNumericValue(c)); - } - - return numbers; - } - - private String tempOutputRouter(StrikeZoneResult result) { - StrikeZoneResult.Type resultType = result.getType(); - if (resultType == StrikeZoneResult.Type.COMPLETE) { - return result.getStrike() + "개의 숫자를 모두 맞히셨습니다! "; - } - if (resultType == StrikeZoneResult.Type.NOTHING) { - return "낫싱\n"; - } - int ballCnt = result.getBall(); - int strikeCnt = result.getStrike(); - List resList = new ArrayList<>(); - - if (strikeCnt > 0) - resList.add(strikeCnt + "스트라이크"); - if (ballCnt > 0) - resList.add(ballCnt + "볼"); - return String.join(" ", resList) + "\n"; - } }