From f4b48002fb4611fe023072c93facee6d342b360a Mon Sep 17 00:00:00 2001 From: jbw9964 Date: Tue, 3 Feb 2026 17:05:37 +0900 Subject: [PATCH 01/12] docs: update README.md --- README.md | 25 ++++++++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d7e8aee..47032224 100644 --- a/README.md +++ b/README.md @@ -1 +1,24 @@ -# java-baseball-precourse \ No newline at end of file +# java-baseball-precourse + +## 구현 기능 목록 + +### core + +- [ ] 무작위 야구 게임 숫자 생성하는 기능 +- [ ] 컴퓨터 숫자, 사용자 숫자 비교해 결과 계산하는 기능 +- [ ] 야구 게임 숫자, 재시작 여부를 질의하고 게임 동작을 수행하는 기능 + +### io + +- [ ] 평문 string 입력을 야구 게임 숫자로 format 하는 기능 +- [ ] 평문 string 입력을 게임 재시작 Yes or no 로 format 하는 기능 + +- [ ] 사용자 야구 게임 숫자 입력받는 기능 +- [ ] 사용자 게임 재시작 여부 입력받는 기능 + +- [ ] 컴퓨터 숫자, 사용자 숫자 비교 결과 출력하는 기능 + +### app + +- [ ] 게임 시작, 재시작, 종료하는 기능 +- [ ] 게임 시작, 종료 구문 보여주는 기능 From f28d48c8e849212bcf3e15c5c03663f6d7906fbd Mon Sep 17 00:00:00 2001 From: jbw9964 Date: Tue, 3 Feb 2026 17:06:10 +0900 Subject: [PATCH 02/12] =?UTF-8?q?feat(core):=20=EB=AC=B4=EC=9E=91=EC=9C=84?= =?UTF-8?q?=20=EC=95=BC=EA=B5=AC=20=EA=B2=8C=EC=9E=84=20=EC=88=AB=EC=9E=90?= =?UTF-8?q?=EB=A5=BC=20=EC=83=9D=EC=84=B1=ED=95=98=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/main/java/com/core/NumberGenerator.java | 52 +++++++++++++++++++ src/main/java/com/dto/Number.java | 14 +++++ .../java/com/core/NumberGeneratorTest.java | 37 +++++++++++++ 4 files changed, 104 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/core/NumberGenerator.java create mode 100644 src/main/java/com/dto/Number.java create mode 100644 src/test/java/com/core/NumberGeneratorTest.java diff --git a/README.md b/README.md index 47032224..b9ff84c6 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ ### core -- [ ] 무작위 야구 게임 숫자 생성하는 기능 +- [x] 무작위 야구 게임 숫자 생성하는 기능 - [ ] 컴퓨터 숫자, 사용자 숫자 비교해 결과 계산하는 기능 - [ ] 야구 게임 숫자, 재시작 여부를 질의하고 게임 동작을 수행하는 기능 diff --git a/src/main/java/com/core/NumberGenerator.java b/src/main/java/com/core/NumberGenerator.java new file mode 100644 index 00000000..4eb31ee0 --- /dev/null +++ b/src/main/java/com/core/NumberGenerator.java @@ -0,0 +1,52 @@ +package com.core; + +import com.dto.Number; +import java.util.Random; + +/** + * 무작위 {@link Number} 생성해주는 클래스 + */ +public class NumberGenerator { + + private final Random rand; + private final int lowerBoundInclusive, upperBoundExclusive; + + /** + * @param lowerBoundInclusive {@code bound <= (생성되는 수)} + * @param upperBoundExclusive {@code (생성되는 수) < bound} + */ + public NumberGenerator(Random rand, int lowerBoundInclusive, int upperBoundExclusive) { + this.rand = rand; + this.lowerBoundInclusive = lowerBoundInclusive; + this.upperBoundExclusive = upperBoundExclusive; + + if (lowerBoundInclusive >= upperBoundExclusive) { + throw new IllegalArgumentException("Lower bound must less than upper bound"); + } + } + + /** + * 랜덤한 {@link Number} 를 생성해 제공한다. + */ + public Number getRandomNumber() { + boolean[] recordTable = new boolean[upperBoundExclusive - lowerBoundInclusive]; + + int first = getRandomNumberExcept(recordTable); + int second = getRandomNumberExcept(recordTable); + int third = getRandomNumberExcept(recordTable); + + return new Number(first, second, third); + } + + private int getRandomNumberExcept(boolean[] numberTable) { + while (true) { + int randNum = rand.nextInt(lowerBoundInclusive, upperBoundExclusive); + int i = randNum - lowerBoundInclusive; + + if (!numberTable[i]) { + numberTable[i] = true; + return randNum; + } + } + } +} diff --git a/src/main/java/com/dto/Number.java b/src/main/java/com/dto/Number.java new file mode 100644 index 00000000..f455c42a --- /dev/null +++ b/src/main/java/com/dto/Number.java @@ -0,0 +1,14 @@ +package com.dto; + +/** + * 야구 게임의 3 자리 숫자 + */ +public record Number(int first, int second, int third) { + + /** + * 제공한 {@code number} 가 포함되어 있는지 여부 + */ + public boolean contains(int number) { + return number == first || number == second || number == third; + } +} diff --git a/src/test/java/com/core/NumberGeneratorTest.java b/src/test/java/com/core/NumberGeneratorTest.java new file mode 100644 index 00000000..5f512978 --- /dev/null +++ b/src/test/java/com/core/NumberGeneratorTest.java @@ -0,0 +1,37 @@ +package com.core; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.dto.Number; +import java.util.Random; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class NumberGeneratorTest { + + private static final int TEST_SIZE = 100, + lowerBoundInclusive = 1, + upperBoundExclusive = 10; + private static final NumberGenerator randNumberGenerator + = new NumberGenerator( + new Random(), lowerBoundInclusive, upperBoundExclusive + ); + + @Test + @DisplayName("1 부터 9 까지 서로 다른 수로 이루어진 3 자리 수를 생성할 수 있다.") + void getRandomNumber() { + for (int i = 0; i < TEST_SIZE; i++) { + Number result = randNumberGenerator.getRandomNumber(); + + assertThat(result).isNotNull().hasNoNullFieldsOrProperties(); + + int first = result.first(); + int second = result.second(); + int third = result.third(); + + assertThat(first).isNotEqualTo(second); + assertThat(second).isNotEqualTo(third); + assertThat(first).isNotEqualTo(third); + } + } +} From 795a61a20d06c94884c3a79ce8a2bbeacf72903d Mon Sep 17 00:00:00 2001 From: jbw9964 Date: Tue, 3 Feb 2026 17:07:11 +0900 Subject: [PATCH 03/12] =?UTF-8?q?feat(core):=20=EB=AC=B4=EC=9E=91=EC=9C=84?= =?UTF-8?q?=20=EC=95=BC=EA=B5=AC=20=EA=B2=8C=EC=9E=84=20=EC=88=AB=EC=9E=90?= =?UTF-8?q?=EB=A5=BC=20=EC=83=9D=EC=84=B1=ED=95=98=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/main/java/com/core/Calculator.java | 61 +++++++ src/main/java/com/dto/Score.java | 35 ++++ src/test/java/com/core/CalculatorTest.java | 183 +++++++++++++++++++++ 4 files changed, 280 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/core/Calculator.java create mode 100644 src/main/java/com/dto/Score.java create mode 100644 src/test/java/com/core/CalculatorTest.java diff --git a/README.md b/README.md index b9ff84c6..e6f3bf2f 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ### core - [x] 무작위 야구 게임 숫자 생성하는 기능 -- [ ] 컴퓨터 숫자, 사용자 숫자 비교해 결과 계산하는 기능 +- [x] 컴퓨터 숫자, 사용자 숫자 비교해 결과 계산하는 기능 - [ ] 야구 게임 숫자, 재시작 여부를 질의하고 게임 동작을 수행하는 기능 ### io diff --git a/src/main/java/com/core/Calculator.java b/src/main/java/com/core/Calculator.java new file mode 100644 index 00000000..8e2b800b --- /dev/null +++ b/src/main/java/com/core/Calculator.java @@ -0,0 +1,61 @@ +package com.core; + +import com.dto.Number; +import com.dto.Score; + +/** + * 두 {@link Number} 로 야구 게임 결과를 계산하는 클래스 + */ +public class Calculator { + + /** + * 컴퓨터, 유저 숫자를 비교해 결과를 계산해 제공한다. + * + * @param gameNumber 컴퓨터 숫자 + * @param userNumber 유저 숫자 + */ + public Score calcScore(Number gameNumber, Number userNumber) { + int numOfStrikes = this.calcNumOfStrikes(gameNumber, userNumber); + int numOfBalls = this.calcNumOfBalls(gameNumber, userNumber); + return new Score(numOfStrikes, numOfBalls); + } + + private int calcNumOfStrikes(Number gameNumber, Number userNumber) { + int cnt = 0; + + if (gameNumber.first() == userNumber.first()) { + cnt++; + } + + if (gameNumber.second() == userNumber.second()) { + cnt++; + } + + if (gameNumber.third() == userNumber.third()) { + cnt++; + } + + return cnt; + } + + private int calcNumOfBalls(Number gameNumber, Number userNumber) { + int cnt = 0; + int userNumFirst = userNumber.first(); + int userNumSecond = userNumber.second(); + int userNumThird = userNumber.third(); + + if (gameNumber.first() != userNumFirst && gameNumber.contains(userNumFirst)) { + cnt++; + } + + if (gameNumber.second() != userNumSecond && gameNumber.contains(userNumSecond)) { + cnt++; + } + + if (gameNumber.third() != userNumThird && gameNumber.contains(userNumThird)) { + cnt++; + } + + return cnt; + } +} diff --git a/src/main/java/com/dto/Score.java b/src/main/java/com/dto/Score.java new file mode 100644 index 00000000..be94b754 --- /dev/null +++ b/src/main/java/com/dto/Score.java @@ -0,0 +1,35 @@ +package com.dto; + +/** + * 한 야구 게임 제출에 대한 결과 + */ +public record Score(int numOfStrikes, int numOfBalls) { + + public Score { + if ( + numOfStrikes < 0 || numOfStrikes > 3 + ) { + throw new IllegalArgumentException("Strikes must be [0, 3] range"); + } + + if ( + numOfBalls < 0 || numOfBalls > 3 + ) { + throw new IllegalArgumentException("Balls must be [0, 3] range"); + } + } + + /** + * {@code 0 점} 인지에 대한 여부 + */ + public boolean isNothing() { + return numOfStrikes + numOfBalls == 0; + } + + /** + * {@code 만점} 인지에 대한 여부 + */ + public boolean isPerfect() { + return numOfStrikes == 3; + } +} diff --git a/src/test/java/com/core/CalculatorTest.java b/src/test/java/com/core/CalculatorTest.java new file mode 100644 index 00000000..1f94e1ae --- /dev/null +++ b/src/test/java/com/core/CalculatorTest.java @@ -0,0 +1,183 @@ +package com.core; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.dto.Number; +import com.dto.Score; +import java.util.stream.Stream; +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; + +class CalculatorTest { + + private static final Calculator calculator = new Calculator(); + + private static Stream strikeArguments() { + return Stream.of( + Arguments.of( + genNumber(1, 2, 3), genNumber(0, 0, 0), 0 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(1, 0, 0), 1 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(0, 2, 0), 1 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(0, 0, 3), 1 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(1, 2, 0), 2 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(1, 0, 3), 2 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(0, 2, 3), 2 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(1, 2, 3), 3 + ) + ); + } + + private static Stream ballArguments() { + return Stream.of( + Arguments.of( + genNumber(1, 2, 3), genNumber(0, 0, 0), 0 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(0, 1, 0), 1 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(0, 0, 1), 1 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(2, 0, 0), 1 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(0, 0, 2), 1 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(3, 0, 0), 1 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(0, 3, 0), 1 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(0, 1, 2), 2 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(2, 0, 1), 2 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(2, 1, 0), 2 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(0, 3, 1), 2 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(3, 0, 1), 2 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(3, 1, 0), 2 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(0, 3, 2), 2 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(2, 3, 0), 2 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(3, 0, 2), 2 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(3, 1, 2), 3 + ) + ); + } + + private static Stream combinedArguments() { + return Stream.of( + Arguments.of( + genNumber(1, 2, 3), genNumber(1, 0, 2), + 1, 1 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(1, 3, 6), + 1, 1 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(3, 2, 0), + 1, 1 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(8, 2, 1), + 1, 1 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(8, 1, 3), + 1, 1 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(2, 0, 3), + 1, 1 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(1, 3, 2), + 1, 2 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(3, 2, 1), + 1, 2 + ), + Arguments.of( + genNumber(1, 2, 3), genNumber(2, 1, 3), + 1, 2 + ) + ); + } + + private static Number genNumber(int first, int second, int third) { + return new Number(first, second, third); + } + + @ParameterizedTest + @MethodSource("strikeArguments") + @DisplayName("두 Number 사이 스트라이크 개수를 계산할 수 있다.") + void testStrikes(Number gameNumber, Number userNumber, int expectedStrikes) { + + Score result = calculator.calcScore(gameNumber, userNumber); + + assertThat(result).isNotNull(); + assertThat(result.numOfStrikes()).isEqualTo(expectedStrikes); + } + + @ParameterizedTest + @MethodSource("ballArguments") + @DisplayName("두 Number 사이 볼 개수를 계산할 수 있다.") + void testBalls(Number gameNumber, Number userNumber, int expectedBalls) { + + Score result = calculator.calcScore(gameNumber, userNumber); + + assertThat(result).isNotNull(); + assertThat(result.numOfBalls()).isEqualTo(expectedBalls); + } + + @ParameterizedTest + @MethodSource("combinedArguments") + @DisplayName("두 Number 사이 스트라이크, 볼 개수를 계산할 수 있다.") + void testCombinedCase( + Number gameNumber, Number userNumber, + int expectedStrikes, int expectedBalls + ) { + + Score result = calculator.calcScore(gameNumber, userNumber); + + assertThat(result).isNotNull(); + assertThat(result.numOfStrikes()).isEqualTo(expectedStrikes); + assertThat(result.numOfBalls()).isEqualTo(expectedBalls); + } +} From 5346089a7faf67ef0618a31b47c7f299ff4c45c3 Mon Sep 17 00:00:00 2001 From: jbw9964 Date: Tue, 3 Feb 2026 17:08:05 +0900 Subject: [PATCH 04/12] =?UTF-8?q?feat(io):=20=ED=8F=89=EB=AC=B8=20string?= =?UTF-8?q?=20=EC=9D=84=20=EC=9B=90=ED=95=98=EB=8A=94=20=ED=98=95=ED=83=9C?= =?UTF-8?q?=EB=A1=9C=20format=20=ED=95=98=EB=8A=94=20=EA=B3=84=EC=95=BD=20?= =?UTF-8?q?=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/exception/InvalidFormatException.java | 8 +++++++ src/main/java/com/io/Formatter.java | 21 +++++++++++++++++++ 2 files changed, 29 insertions(+) create mode 100644 src/main/java/com/exception/InvalidFormatException.java create mode 100644 src/main/java/com/io/Formatter.java diff --git a/src/main/java/com/exception/InvalidFormatException.java b/src/main/java/com/exception/InvalidFormatException.java new file mode 100644 index 00000000..671d6821 --- /dev/null +++ b/src/main/java/com/exception/InvalidFormatException.java @@ -0,0 +1,8 @@ +package com.exception; + +public class InvalidFormatException extends RuntimeException { + + public InvalidFormatException(String message) { + super(message); + } +} diff --git a/src/main/java/com/io/Formatter.java b/src/main/java/com/io/Formatter.java new file mode 100644 index 00000000..2b901a68 --- /dev/null +++ b/src/main/java/com/io/Formatter.java @@ -0,0 +1,21 @@ +package com.io; + +import com.exception.InvalidFormatException; + +/** + * 평문 문자열 {@code (input)} 을 원하는 형태로 format 하는 방법에 대한 계약 + * + * @param format 되길 원하는 형태 + */ +public interface Formatter { + + /** + * new line input 으로부터 원하는 {@code T} 로 format 하는 메서드 + *

+ * Format 안맞으면 그냥 Ex throw 하거나 {@code null} return + * + * @param input {@code br.readLine()} 으로 받은 new line + * @throws InvalidFormatException {@code input} 으로부터 {@code T} 를 만들 수 없을 때 + */ + T format(String input) throws InvalidFormatException; +} From de45a428d1bde43e3a332f96739a4919a93d66de Mon Sep 17 00:00:00 2001 From: jbw9964 Date: Tue, 3 Feb 2026 17:08:50 +0900 Subject: [PATCH 05/12] =?UTF-8?q?feat(io):=20=ED=8F=89=EB=AC=B8=20string?= =?UTF-8?q?=20=EC=9E=85=EB=A0=A5=EC=9D=84=20=EC=95=BC=EA=B5=AC=20=EA=B2=8C?= =?UTF-8?q?=EC=9E=84=20=EC=88=AB=EC=9E=90=EB=A1=9C=20format=20=ED=95=98?= =?UTF-8?q?=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/main/java/com/io/NumberFormatter.java | 54 +++++++++++++++ src/test/java/com/io/NumberFormatterTest.java | 69 +++++++++++++++++++ 3 files changed, 124 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/io/NumberFormatter.java create mode 100644 src/test/java/com/io/NumberFormatterTest.java diff --git a/README.md b/README.md index e6f3bf2f..56946079 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ ### io -- [ ] 평문 string 입력을 야구 게임 숫자로 format 하는 기능 +- [x] 평문 string 입력을 야구 게임 숫자로 format 하는 기능 - [ ] 평문 string 입력을 게임 재시작 Yes or no 로 format 하는 기능 - [ ] 사용자 야구 게임 숫자 입력받는 기능 diff --git a/src/main/java/com/io/NumberFormatter.java b/src/main/java/com/io/NumberFormatter.java new file mode 100644 index 00000000..714a53fb --- /dev/null +++ b/src/main/java/com/io/NumberFormatter.java @@ -0,0 +1,54 @@ +package com.io; + +import com.dto.Number; +import com.exception.InvalidFormatException; + +/** + * 콘솔 입력을 {@link Number} 로 format 해주는 클래스 + */ +public class NumberFormatter implements Formatter { + + private static final char ZERO = '0'; + + @Override + public Number format(String input) throws InvalidFormatException { + if ( + input == null || + (input = input.trim()).length() != 3 + ) { + throw new InvalidFormatException("Invalid input"); + } + + int first, second, third; + + // input 에서 각 자리 숫자를 가져온다. + first = parseNumberOnIndex(input, 0); + second = parseNumberOnIndex(input, 1); + third = parseNumberOnIndex(input, 2); + + // 3 숫자는 서로 다른 숫자여야 한다. + if (first == second || second == third || first == third) { + throw new InvalidFormatException("Invalid input"); + } + + return new Number(first, second, third); + } + + private int parseNumberOnIndex(String str, int index) { + if (str.length() <= index) { + throw new InvalidFormatException("Invalid number format index"); + } + + char c = str.charAt(index); + + if (!Character.isDigit(c)) { + throw new InvalidFormatException("Not a number"); + } + + if (c <= ZERO) { + throw new InvalidFormatException("Only 1 - 9 digits available"); + } + + return c - ZERO; + } +} diff --git a/src/test/java/com/io/NumberFormatterTest.java b/src/test/java/com/io/NumberFormatterTest.java new file mode 100644 index 00000000..5ff344bf --- /dev/null +++ b/src/test/java/com/io/NumberFormatterTest.java @@ -0,0 +1,69 @@ +package com.io; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +import com.dto.Number; +import com.exception.InvalidFormatException; +import java.util.stream.Stream; +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; + +class NumberFormatterTest { + + private static final NumberFormatter formater = new NumberFormatter(); + + private static Stream validArguments() { + return Stream.of( + Arguments.of("123", new Number(1, 2, 3)), + Arguments.of("321", new Number(3, 2, 1)), + Arguments.of("456", new Number(4, 5, 6)), + Arguments.of("654", new Number(6, 5, 4)), + Arguments.of("789", new Number(7, 8, 9)), + Arguments.of("987", new Number(9, 8, 7)) + ); + } + + private static Stream invalidArguments() { + return Stream.of( + Arguments.of("012"), + Arguments.of("102"), + Arguments.of("120"), + Arguments.of("121"), + Arguments.of("122"), + Arguments.of("221"), + Arguments.of("222"), + Arguments.of("a12"), + Arguments.of("1 2"), + Arguments.of("12."), + Arguments.of("abc"), + Arguments.of("1"), + Arguments.of("12"), + Arguments.of(""), + Arguments.of(" "), + Arguments.of((Object) null) + ); + } + + @ParameterizedTest + @MethodSource("validArguments") + @DisplayName("숫자 형식의 입력을 format 할 수 있다.") + void format(String input, Number expected) { + Number result = formater.format(input); + + assertThat(result).isNotNull() + .hasNoNullFieldsOrProperties() + .isEqualTo(expected); + } + + @ParameterizedTest + @MethodSource("invalidArguments") + @DisplayName("올바르지 않은 입력은 InvalidFormatException 에러를 일으킨다.") + void testInvalidFormatException(String input) { + + assertThatThrownBy(() -> formater.format(input)) + .isInstanceOf(InvalidFormatException.class); + } +} \ No newline at end of file From c0b12601e1de5f9ea028ba355c24c43973ce614d Mon Sep 17 00:00:00 2001 From: jbw9964 Date: Tue, 3 Feb 2026 17:09:14 +0900 Subject: [PATCH 06/12] =?UTF-8?q?feat(io):=20=ED=8F=89=EB=AC=B8=20string?= =?UTF-8?q?=20=EC=9E=85=EB=A0=A5=EC=9D=84=20=EA=B2=8C=EC=9E=84=20=EC=9E=AC?= =?UTF-8?q?=EC=8B=9C=EC=9E=91=20Yes=20or=20no=20=EB=A1=9C=20format=20?= =?UTF-8?q?=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/main/java/com/io/YesOrNoFormatter.java | 29 ++++++++++ .../java/com/io/YesOrNoFormatterTest.java | 55 +++++++++++++++++++ 3 files changed, 85 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/io/YesOrNoFormatter.java create mode 100644 src/test/java/com/io/YesOrNoFormatterTest.java diff --git a/README.md b/README.md index 56946079..6b776c73 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ ### io - [x] 평문 string 입력을 야구 게임 숫자로 format 하는 기능 -- [ ] 평문 string 입력을 게임 재시작 Yes or no 로 format 하는 기능 +- [x] 평문 string 입력을 게임 재시작 Yes or no 로 format 하는 기능 - [ ] 사용자 야구 게임 숫자 입력받는 기능 - [ ] 사용자 게임 재시작 여부 입력받는 기능 diff --git a/src/main/java/com/io/YesOrNoFormatter.java b/src/main/java/com/io/YesOrNoFormatter.java new file mode 100644 index 00000000..3110c23b --- /dev/null +++ b/src/main/java/com/io/YesOrNoFormatter.java @@ -0,0 +1,29 @@ +package com.io; + +import com.exception.InvalidFormatException; + +/** + * 콘솔 입력을 {@code true(yes)} or {@code false(no)} 로 format 해 제공하는 클래스 + */ +public class YesOrNoFormatter implements Formatter { + + @Override + public Boolean format(String input) throws InvalidFormatException { + boolean result = false; + + if (input != null && !input.isEmpty()) { + + // 확실히 yes 인 것들만 추려낸다. + if ( + input.equals("Y") || + input.equals("YES") || + input.equals("y") || + input.equals("yes") + ) { + result = true; + } + } + + return result; + } +} diff --git a/src/test/java/com/io/YesOrNoFormatterTest.java b/src/test/java/com/io/YesOrNoFormatterTest.java new file mode 100644 index 00000000..f6c5cc7a --- /dev/null +++ b/src/test/java/com/io/YesOrNoFormatterTest.java @@ -0,0 +1,55 @@ +package com.io; + +import static org.assertj.core.api.Assertions.assertThat; + +import java.util.stream.Stream; +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; + +class YesOrNoFormatterTest { + + private static final YesOrNoFormatter formatter = new YesOrNoFormatter(); + + private static Stream yesArguments() { + return Stream.of( + Arguments.of("yes"), + Arguments.of("y"), + Arguments.of("YES"), + Arguments.of("Y") + ); + } + + private static Stream noArguments() { + return Stream.of( + Arguments.of("no"), + Arguments.of("n"), + Arguments.of("this is invalid"), + Arguments.of("i am Groot"), + Arguments.of(""), + Arguments.of(" "), + Arguments.of((Object) null) + ); + } + + @ParameterizedTest + @MethodSource("yesArguments") + @DisplayName("Yes 입력을 format 할 수 있다.") + void format(String input) { + + Boolean result = formatter.format(input); + + assertThat(result).isNotNull().isTrue(); + } + + @ParameterizedTest + @MethodSource("noArguments") + @DisplayName("정확히 YES 가 아닌 모든 입력은 false 로 format 된다.") + void testNoFormats(String input) { + + Boolean result = formatter.format(input); + + assertThat(result).isNotNull().isFalse(); + } +} From a38e93a4d04d9c2e6b2282ea9d918c6cd4af270b Mon Sep 17 00:00:00 2001 From: jbw9964 Date: Tue, 3 Feb 2026 17:09:44 +0900 Subject: [PATCH 07/12] =?UTF-8?q?feat(io):=20`BufferedReader`=20=EB=A5=BC?= =?UTF-8?q?=20=ED=86=B5=ED=95=B4=20=EC=BD=98=EC=86=94=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=EC=9D=84=20=EC=9B=90=ED=95=98=EB=8A=94=20=ED=98=95=ED=83=9C?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=9C=EA=B3=B5=ED=95=98=EB=8A=94=20=EC=B6=94?= =?UTF-8?q?=EC=83=81=20=ED=81=B4=EB=9E=98=EC=8A=A4=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/io/AbstractInputReceiver.java | 59 +++++++++++++++++++ 1 file changed, 59 insertions(+) create mode 100644 src/main/java/com/io/AbstractInputReceiver.java diff --git a/src/main/java/com/io/AbstractInputReceiver.java b/src/main/java/com/io/AbstractInputReceiver.java new file mode 100644 index 00000000..4855752f --- /dev/null +++ b/src/main/java/com/io/AbstractInputReceiver.java @@ -0,0 +1,59 @@ +package com.io; + +import com.exception.InvalidFormatException; +import java.io.BufferedReader; +import java.io.IOException; +import java.util.Optional; +import java.util.logging.Logger; + +/** + * 콘솔 입력을 {@code T} 로 만들어 주는 추상 클래스 + * + * @param format 되길 원하는 형태 + * @see Formatter + */ +public abstract class AbstractInputReceiver { + + private final BufferedReader br; + private final Formatter formatter; + + private final Logger log; + private final boolean DEBUG; + + protected AbstractInputReceiver(BufferedReader br, Formatter formatter) { + this(br, formatter, false); + } + + protected AbstractInputReceiver(BufferedReader br, Formatter formatter, boolean debug) { + this.br = br; + this.formatter = formatter; + DEBUG = debug; + this.log = Logger.getLogger(this.getClass().getName()); + } + + /** + * IO 로부터 입력받아 원하는 형태로 format, Optional 로 감싸 제공하는 메서드 + * + * @return format 하다 실패했을 땐 {@code Optional.empty()} + * @see Formatter#format(String) + */ + public final Optional receiveInput() { + T result = null; + + try { + String input = br.readLine(); // io 로 입력을 받아 + result = formatter.format(input); // formatter 로 정제한다. + } catch (IOException | InvalidFormatException e) { + // 에러 터지면 유감 + if (DEBUG) { + log.info(String.format( + "Failed to format input: (%s)%s", + e.getClass().getSimpleName(), + e.getMessage() + )); + } + } + + return Optional.ofNullable(result); + } +} From f40e939452d033fb9a05e633d29096d7d3078d76 Mon Sep 17 00:00:00 2001 From: jbw9964 Date: Tue, 3 Feb 2026 17:10:32 +0900 Subject: [PATCH 08/12] =?UTF-8?q?feat(io):=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=20=EC=95=BC=EA=B5=AC=20=EA=B2=8C=EC=9E=84=20=EC=88=AB=EC=9E=90?= =?UTF-8?q?=20=EC=9E=85=EB=A0=A5=EB=B0=9B=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/main/java/com/io/NumberReceiver.java | 14 ++++++++++++++ 2 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/io/NumberReceiver.java diff --git a/README.md b/README.md index 6b776c73..ac1f10c4 100644 --- a/README.md +++ b/README.md @@ -13,7 +13,7 @@ - [x] 평문 string 입력을 야구 게임 숫자로 format 하는 기능 - [x] 평문 string 입력을 게임 재시작 Yes or no 로 format 하는 기능 -- [ ] 사용자 야구 게임 숫자 입력받는 기능 +- [x] 사용자 야구 게임 숫자 입력받는 기능 - [ ] 사용자 게임 재시작 여부 입력받는 기능 - [ ] 컴퓨터 숫자, 사용자 숫자 비교 결과 출력하는 기능 diff --git a/src/main/java/com/io/NumberReceiver.java b/src/main/java/com/io/NumberReceiver.java new file mode 100644 index 00000000..ef09992f --- /dev/null +++ b/src/main/java/com/io/NumberReceiver.java @@ -0,0 +1,14 @@ +package com.io; + +import com.dto.Number; +import java.io.BufferedReader; + +/** + * 야구 게임 숫자를 입력 받아 {@link Number} 로 format 해 제공하는 클래스 + */ +public class NumberReceiver extends AbstractInputReceiver { + + public NumberReceiver(BufferedReader br, NumberFormatter numberFormatter) { + super(br, numberFormatter); + } +} From 0174c667e5ceae4045076d0cb32a9873a7e66ab2 Mon Sep 17 00:00:00 2001 From: jbw9964 Date: Tue, 3 Feb 2026 17:10:55 +0900 Subject: [PATCH 09/12] =?UTF-8?q?feat(io):=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=20=EA=B2=8C=EC=9E=84=20=EC=9E=AC=EC=8B=9C=EC=9E=91=20=EC=97=AC?= =?UTF-8?q?=EB=B6=80=20=EC=9E=85=EB=A0=A5=EB=B0=9B=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/main/java/com/io/YesOrNoReceiver.java | 13 +++++++++++++ 2 files changed, 14 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/io/YesOrNoReceiver.java diff --git a/README.md b/README.md index ac1f10c4..1748649e 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ - [x] 평문 string 입력을 게임 재시작 Yes or no 로 format 하는 기능 - [x] 사용자 야구 게임 숫자 입력받는 기능 -- [ ] 사용자 게임 재시작 여부 입력받는 기능 +- [x] 사용자 게임 재시작 여부 입력받는 기능 - [ ] 컴퓨터 숫자, 사용자 숫자 비교 결과 출력하는 기능 diff --git a/src/main/java/com/io/YesOrNoReceiver.java b/src/main/java/com/io/YesOrNoReceiver.java new file mode 100644 index 00000000..aaa1c4fe --- /dev/null +++ b/src/main/java/com/io/YesOrNoReceiver.java @@ -0,0 +1,13 @@ +package com.io; + +import java.io.BufferedReader; + +/** + * 야구 게임을 완료했을 때 재시작 입력을 받아 {@code Boolean} 으로 format 해 제공하는 클래스 + */ +public class YesOrNoReceiver extends AbstractInputReceiver { + + public YesOrNoReceiver(BufferedReader br, YesOrNoFormatter yesOrNoFormatter) { + super(br, yesOrNoFormatter); + } +} From e0124ee2707e33e4e93d9881404910554212baaa Mon Sep 17 00:00:00 2001 From: jbw9964 Date: Tue, 3 Feb 2026 17:11:26 +0900 Subject: [PATCH 10/12] =?UTF-8?q?feat(io):=20=EC=BB=B4=ED=93=A8=ED=84=B0?= =?UTF-8?q?=20=EC=88=AB=EC=9E=90,=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=88=AB=EC=9E=90=20=EB=B9=84=EA=B5=90=20=EA=B2=B0=EA=B3=BC=20?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=ED=95=98=EB=8A=94=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/main/java/com/io/Printer.java | 63 +++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/io/Printer.java diff --git a/README.md b/README.md index 1748649e..3ea17002 100644 --- a/README.md +++ b/README.md @@ -16,7 +16,7 @@ - [x] 사용자 야구 게임 숫자 입력받는 기능 - [x] 사용자 게임 재시작 여부 입력받는 기능 -- [ ] 컴퓨터 숫자, 사용자 숫자 비교 결과 출력하는 기능 +- [x] 컴퓨터 숫자, 사용자 숫자 비교 결과 출력하는 기능 ### app diff --git a/src/main/java/com/io/Printer.java b/src/main/java/com/io/Printer.java new file mode 100644 index 00000000..5c72c700 --- /dev/null +++ b/src/main/java/com/io/Printer.java @@ -0,0 +1,63 @@ +package com.io; + +import com.dto.Score; + +public class Printer { + + @SuppressWarnings("unused") + public static final String + RED = "\u001B[31m", + GREEN = "\u001B[32m", + YELLOW = "\u001B[33m", + BLUE = "\u001B[34m", + MAGENTA = "\u001B[35m", + CYAN = "\u001B[36m", + RESET = "\33[39m"; + + + public void printNumberInputQuery() { + System.out.printf(""" + %s----<: 숫자를 입력해주세요 :%s\s""", + CYAN, RESET); + } + + public void printInvalidNumberInput() { + System.out.printf(""" + %s----x: 입력이 올바르지 않습니다... (x.x) + : 1 - 9 범위의 숫자 3 개를 중복없이 제공해 주세요!%s + + """, + RED, RESET + ); + } + + public void printScore(Score score) { + if (score.isNothing()) { + System.out.printf(""" + %s---->: [ Nothing ] \t아무런 숫자도 겹치지 않네요 \\(i,i)/%s + + """, + YELLOW, RESET + ); + return; + } + + int numOfStrikes = score.numOfStrikes(); + int numOfBalls = score.numOfBalls(); + + System.out.printf(""" + %s---->: [ 스트라이크 : %1d, 볼 : %1d ] (O.O)%s + + """, + BLUE, numOfStrikes, numOfBalls, RESET + ); + } + + public void printGameRetryQuery() { + System.out.printf(""" + %s----o: 정답입니다! (^o^) + : 게임을 다시 시작하시겠습니까? (Y):%s\s""", + MAGENTA, RESET + ); + } +} From cb7d6f1559f3fb17b6c7e86963efd65c45f478e4 Mon Sep 17 00:00:00 2001 From: jbw9964 Date: Tue, 3 Feb 2026 17:15:38 +0900 Subject: [PATCH 11/12] =?UTF-8?q?feat(core):=20=EC=95=BC=EA=B5=AC=20?= =?UTF-8?q?=EA=B2=8C=EC=9E=84=20=EC=88=AB=EC=9E=90,=20=EC=9E=AC=EC=8B=9C?= =?UTF-8?q?=EC=9E=91=20=EC=97=AC=EB=B6=80=EB=A5=BC=20=EC=A7=88=EC=9D=98?= =?UTF-8?q?=ED=95=98=EA=B3=A0=20=EA=B2=8C=EC=9E=84=20=EB=8F=99=EC=9E=91?= =?UTF-8?q?=EC=9D=84=20=EC=88=98=ED=96=89=ED=95=98=EB=8A=94=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 2 +- src/main/java/com/core/Game.java | 84 +++++++++++ src/test/java/com/core/GameTest.java | 218 +++++++++++++++++++++++++++ 3 files changed, 303 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/core/Game.java create mode 100644 src/test/java/com/core/GameTest.java diff --git a/README.md b/README.md index 3ea17002..1b775b78 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ - [x] 무작위 야구 게임 숫자 생성하는 기능 - [x] 컴퓨터 숫자, 사용자 숫자 비교해 결과 계산하는 기능 -- [ ] 야구 게임 숫자, 재시작 여부를 질의하고 게임 동작을 수행하는 기능 +- [x] 야구 게임 숫자, 재시작 여부를 질의하고 게임 동작을 수행하는 기능 ### io diff --git a/src/main/java/com/core/Game.java b/src/main/java/com/core/Game.java new file mode 100644 index 00000000..a246c00d --- /dev/null +++ b/src/main/java/com/core/Game.java @@ -0,0 +1,84 @@ +package com.core; + +import com.dto.Number; +import com.dto.Score; +import com.io.NumberReceiver; +import com.io.Printer; +import com.io.YesOrNoReceiver; +import java.util.Optional; + +/** + * 어느 한 야구 게임을 의미하는 클래스 + */ +public class Game { + + /** + * 게임 정답 + */ + private final Number gameNumber; + + private final Printer printer; + private final NumberReceiver numberReceiver; + private final YesOrNoReceiver yesOrNoReceiver; + + private final Calculator calculator; + + public Game( + Number gameNumber, Printer printer, + NumberReceiver numberReceiver, YesOrNoReceiver yesOrNoReceiver, + Calculator calculator + ) { + this.gameNumber = gameNumber; + this.printer = printer; + this.numberReceiver = numberReceiver; + this.yesOrNoReceiver = yesOrNoReceiver; + this.calculator = calculator; + } + + /** + * 콘솔로 숫자를 입력받아 제공한다. + *

+ * 올바른 입력을 받을때까지 무한정 loop 한다. + */ + public Number queryNumberInputAndReceive() { + Number userNumber = null; + + while (userNumber == null) { + // 숫자 입력해달라고 출력한다. + printer.printNumberInputQuery(); + + // 입력 받는다. + Optional opt = numberReceiver.receiveInput(); + + // 올바르지 않은 입력이다. + if (opt.isEmpty()) { + printer.printInvalidNumberInput(); + continue; + } + + userNumber = opt.get(); + } + + return userNumber; + } + + /** + * 게임 정답과 비교해 결과를 생성하고 콘솔에 출력한다. + */ + public Score calcScoreAndPrintResult(Number userNumber) { + Score score = calculator.calcScore(gameNumber, userNumber); + + printer.printScore(score); + + return score; + } + + /** + * 콘솔로 게임 재시작 여부 입력을 받는다. + */ + public boolean queryRetryYesOrNoAndReceive() { + printer.printGameRetryQuery(); + return yesOrNoReceiver.receiveInput() + .orElse(false); + } +} diff --git a/src/test/java/com/core/GameTest.java b/src/test/java/com/core/GameTest.java new file mode 100644 index 00000000..f1f21289 --- /dev/null +++ b/src/test/java/com/core/GameTest.java @@ -0,0 +1,218 @@ +package com.core; + +import static org.assertj.core.api.Assertions.assertThat; + +import com.dto.Number; +import com.io.NumberFormatter; +import com.io.NumberReceiver; +import com.io.Printer; +import com.io.YesOrNoFormatter; +import com.io.YesOrNoReceiver; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.ArrayDeque; +import java.util.Arrays; +import java.util.List; +import java.util.Queue; +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +class GameTest { + + private static final MockedPrinter mockedPrinter; + private static final MockedBufferedReader mockedBufferedReader; + + private static final NumberFormatter numberFormatter; + private static final NumberReceiver numberReceiver; + private static final YesOrNoFormatter yesOrNoFormatter; + private static final YesOrNoReceiver yesOrNoReceiver; + + private static final Calculator calculator; + + static { + mockedPrinter = new MockedPrinter(); + mockedBufferedReader = new MockedBufferedReader(); + + numberFormatter = new NumberFormatter(); + yesOrNoFormatter = new YesOrNoFormatter(); + + numberReceiver = new NumberReceiver( + mockedBufferedReader, numberFormatter + ); + yesOrNoReceiver = new YesOrNoReceiver( + mockedBufferedReader, yesOrNoFormatter + ); + + calculator = new Calculator(); + } + + @AfterEach + void tearDown() { + mockedPrinter.initCallCnt(); + mockedBufferedReader + .initResponses() + .initMethodCallCnt(); + } + + @Test + @DisplayName("올바른 숫자 입력이 제공될때까지 입력을 게속 받는다.") + void testNumberInput() { + List invalidInputs = Arrays.asList( + "", " ", + "1 ", " 1", " 1 ", "a", "0", "1", "?", "/", + "10", "11", "1a", "a1", "1 ", + "120", "121", "1 3", "1a3" + ); + String validInput = "123"; + + // input 을 setup 해둔다. + mockedBufferedReader + .addResponses(invalidInputs) + .addResponse(validInput); + + int invalidInputTestSize = invalidInputs.size(); + int testSize = invalidInputTestSize + 1; + + // game 만들어서 number input 을 넣어본다. + Game game = this.createGame(); + game.queryNumberInputAndReceive(); + + // 예상대로 돌아갔는지 검증한다. + assertThat(mockedPrinter.getPrintInvalidNumberInputCallCnt()) + .isEqualTo(invalidInputTestSize); + assertThat(mockedPrinter.getPrintNumberInputQueryCallCnt()) + .isEqualTo(testSize); + + assertThat(mockedBufferedReader.getMethodCallCnt()) + .isEqualTo(testSize); + } + + @Test + @DisplayName("확실히 Yes 가 아닌 입력들은 모두 No 로 처리된다.") + void testYesOrNoInput() { + List noInputs = Arrays.asList( + "", " ", + "1 ", " 1", " 1 ", "a", "0", "1", "?", "/", + "10", "11", "1a", "a1", "1 ", + "120", "121", "1 3", "1a3" + ); + List yesInputs = Arrays.asList( + "y", "yes", "Y", "YES" + ); + + // input 을 setup 해둔다. + mockedBufferedReader + .addResponses(noInputs) + .addResponses(yesInputs); + + int noInputTestSize = noInputs.size(); + int yesInputTestSize = yesInputs.size(); + + // game 만들어서 + Game game = this.createGame(); + + // yes or no input 을 넣어 결과를 검증한다. + for (int i = 0; i < noInputTestSize; i++) { + assertThat(game.queryRetryYesOrNoAndReceive()).isFalse(); + } + + for (int i = 0; i < yesInputTestSize; i++) { + assertThat(game.queryRetryYesOrNoAndReceive()).isTrue(); + } + } + + Game createGame() { + return new Game( + new Number(1, 2, 3), + mockedPrinter, numberReceiver, yesOrNoReceiver, + calculator + ); + } +} + +class MockedPrinter extends Printer { + + private int + printNumberInputQueryCallCnt, + printInvalidNumberInputCallCnt; + + @Override + public void printNumberInputQuery() { + printNumberInputQueryCallCnt++; + } + + @Override + public void printInvalidNumberInput() { + printInvalidNumberInputCallCnt++; + } + + @Override + public void printGameRetryQuery() { + // ignore + } + + public int getPrintNumberInputQueryCallCnt() { + return printNumberInputQueryCallCnt; + } + + public int getPrintInvalidNumberInputCallCnt() { + return printInvalidNumberInputCallCnt; + } + + public void initCallCnt() { + printInvalidNumberInputCallCnt = 0; + } +} + +@SuppressWarnings("UnusedReturnValue") +class MockedBufferedReader extends BufferedReader { + + private final Queue responseList; + private int methodCallCnt; + + public MockedBufferedReader() { + super(new InputStreamReader(System.in)); + responseList = new ArrayDeque<>(); + } + + @Override + @SuppressWarnings("RedundantThrows") + public String readLine() throws IOException { + + if (responseList.isEmpty()) { + throw new AssertionError( + "No response has been mocked on method: " + + "BufferedReader#readLine" + ); + } + + methodCallCnt++; + return responseList.poll(); + } + + public MockedBufferedReader addResponse(String response) { + responseList.add(response); + return this; + } + + public MockedBufferedReader addResponses(List responses) { + responseList.addAll(responses); + return this; + } + + public MockedBufferedReader initResponses() { + responseList.clear(); + return this; + } + + public int getMethodCallCnt() { + return methodCallCnt; + } + + public MockedBufferedReader initMethodCallCnt() { + methodCallCnt = 0; + return this; + } +} From 12fd452b143f38aba922161c34f028da79683ce1 Mon Sep 17 00:00:00 2001 From: jbw9964 Date: Tue, 3 Feb 2026 17:16:01 +0900 Subject: [PATCH 12/12] =?UTF-8?q?feat(app):=20=EC=A0=84=EC=B2=B4=20?= =?UTF-8?q?=EA=B2=8C=EC=9E=84=EC=9D=84=20=EA=B5=AC=EB=8F=99=ED=95=98?= =?UTF-8?q?=EB=8F=84=EB=A1=9D=20=EA=B5=AC=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- src/main/java/com/Application.java | 165 +++++++++++++++++++++++++++++ 2 files changed, 167 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/Application.java diff --git a/README.md b/README.md index 1b775b78..bc58598d 100644 --- a/README.md +++ b/README.md @@ -20,5 +20,5 @@ ### app -- [ ] 게임 시작, 재시작, 종료하는 기능 -- [ ] 게임 시작, 종료 구문 보여주는 기능 +- [x] 게임 시작, 재시작, 종료하는 기능 +- [x] 게임 시작, 종료 구문 보여주는 기능 diff --git a/src/main/java/com/Application.java b/src/main/java/com/Application.java new file mode 100644 index 00000000..2e68891e --- /dev/null +++ b/src/main/java/com/Application.java @@ -0,0 +1,165 @@ +package com; + +import com.core.Calculator; +import com.core.Game; +import com.core.NumberGenerator; +import com.dto.Number; +import com.dto.Score; +import com.io.NumberFormatter; +import com.io.NumberReceiver; +import com.io.Printer; +import com.io.YesOrNoFormatter; +import com.io.YesOrNoReceiver; +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.security.SecureRandom; +import java.util.Random; +import java.util.logging.Logger; + +public class Application { + + private static final Random RANDOM; + private static final int + NUMBER_LOWER_BOUND_INCLUSIVE, + NUMBER_UPPER_BOUND_EXCLUSIVE; + private static final NumberGenerator NUMBER_GENERATOR; + + private static final BufferedReader BUFFERED_READER; + + private static final Printer PRINTER; + private static final NumberFormatter NUMBER_FORMATTER; + private static final NumberReceiver NUMBER_RECEIVER; + private static final YesOrNoFormatter YES_OR_NO_FORMATTER; + private static final YesOrNoReceiver YES_OR_NO_RECEIVER; + + private static final Calculator CALCULATOR; + + private static final Logger LOGGER; + private static final boolean DEBUG = false; + + static { + RANDOM = new SecureRandom(); + + NUMBER_LOWER_BOUND_INCLUSIVE = 1; + NUMBER_UPPER_BOUND_EXCLUSIVE = 10; + + NUMBER_GENERATOR = new NumberGenerator( + RANDOM, + NUMBER_LOWER_BOUND_INCLUSIVE, NUMBER_UPPER_BOUND_EXCLUSIVE + ); + + BUFFERED_READER = new BufferedReader( + new InputStreamReader(System.in) + ); + + PRINTER = new Printer(); + NUMBER_FORMATTER = new NumberFormatter(); + NUMBER_RECEIVER = new NumberReceiver( + BUFFERED_READER, NUMBER_FORMATTER + ); + YES_OR_NO_FORMATTER = new YesOrNoFormatter(); + YES_OR_NO_RECEIVER = new YesOrNoReceiver( + BUFFERED_READER, YES_OR_NO_FORMATTER + ); + + CALCULATOR = new Calculator(); + + LOGGER = Logger.getLogger(Application.class.getName()); + } + + public static void main(String[] args) { + printWelcomeMessage(); + + // game loop 를 만들어 게임을 시작, 종료, 재시작한다. + Internal gameLoop = new Internal(); + while (true) { + + System.out.print("\n------------------------------------------------------------\n"); + + boolean retryGame = gameLoop.runGame(); + + if (!retryGame) { + System.out.print(""" + + --<: 게임을 종료합니다. (^^)7 + """); + break; + } + } + + printGoodbyeMessage(); + } + + public static void printWelcomeMessage() { + System.out.print(""" + ------------------------------ + | Hello world! | + ------------------------------ + """); + } + + public static void printGoodbyeMessage() { + System.out.print(""" + ------------------------------------------------------------ + + -------------------------------- + | Goodbye world! | + -------------------------------- + """); + } + + private static class Internal { + + /** + * 한 게임을 진행하고 재시작 여부를 반환한다. + * + * @return 게임 재시작 여부 + */ + private boolean runGame() { + System.out.print(""" + \n--o: 게임 난수 생성중... + """); + + // 새로운 게임을 생성한다. + Game game = this.initGame(); + + System.out.print(""" + -->: 게임 난수 생성 완료. 숫자 야구 게임이 시작되었습니다! + + """); + + // 게임이 끝날때까지 진행한다. + while (true) { + Number userNumber = game.queryNumberInputAndReceive(); + + Score score = game.calcScoreAndPrintResult(userNumber); + + if (score.isPerfect()) { + break; + } + } + + // 게임 재시작 여부를 반환한다. + return game.queryRetryYesOrNoAndReceive(); + } + + /** + * 새로운 게임을 생성해 제공한다. + */ + private Game initGame() { + Number gameNumber = NUMBER_GENERATOR.getRandomNumber(); + + System.out.println(gameNumber); + + if (DEBUG) { + LOGGER.info("Game number :" + gameNumber); + } + + return new Game( + gameNumber, PRINTER, + NUMBER_RECEIVER, YES_OR_NO_RECEIVER, + CALCULATOR + ); + } + } +}