From a2a9bcd1de1c480fc469108d332256fbf89c201b Mon Sep 17 00:00:00 2001 From: myeongheon Date: Tue, 7 May 2024 19:35:14 +0900 Subject: [PATCH 01/10] docs: update README.md --- README.md | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 91 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 8d7e8aee..effb309e 100644 --- a/README.md +++ b/README.md @@ -1 +1,91 @@ -# java-baseball-precourse \ No newline at end of file +# 숫자 야구 + +1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임이다. + +## 기능 요구 사항 + +--- + +- 같은 수가 같은 자리에 있으면 스트라이크, 다른 자리에 있으면 볼, 같은 수가 전혀 없으면 낫싱이란 힌트를 얻고, 그 힌트를 이용해서 먼저 상대방(컴퓨터)의 수를 맞추면 승리한다. + +```markdown +e.g. +상대방(컴퓨터)의 수가 425일 때, +- 123을 제시한 경우 : 1스트라이크 +- 456을 제시한 경우 : 1볼 1스트라이크 +- 789를 제시한 경우 : 낫싱 +``` + +- 위 숫자 야구 게임에서 상대방의 역할을 컴퓨터가 한다. 컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다. 게임 플레이어는 컴퓨터가 생각하고 있는 3개의 숫자를 입력하고, 컴퓨터는 입력한 숫자에 대한 결과를 출력한다. +- 이 같은 과정을 반복해 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임이 종료된다. +- 게임을 종료한 후 게임을 다시 시작하거나 완전히 종료할 수 있다. +- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시킨 후 애플리케이션은 종료되어야 한다. + +## 실행 결과 + +--- + +```markdown +숫자를 입력해 주세요 : 123 +1볼 1스트라이크 +숫자를 입력해 주세요 : 145 +1볼 +숫자를 입력해 주세요 : 671 +2볼 +숫자를 입력해 주세요 : 216 +1스트라이크 +숫자를 입력해 주세요 : 713 +3스트라이크 +3개의 숫자를 모두 맞히셨습니다! 게임 종료 +게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요. +1 +숫자를 입력해 주세요 : 123 +1볼 +... +``` + +- 프로그래밍 요구 사항 1 + - JDK 17 버전에서 실행 가능해야 한다. + - 프로그램 실행의 시작점은 Application 의 main() 이다. + - build.gradle 파일은 변경할 수 없으며, 제공된 라이브러리 이외의 외부 라이브러리는 사용하지 않는다. + - 프로그램 종료 시 System.exit() 를 호출하지 않는다. + - 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 등의 이름을 바꾸거나 이동하지 않는다. +- 프로그래밍 요구 사항 2 + - 자바 코드 컨벤션을 지키면서 프로그래밍한다. + - 기본적으로 Google Java Style Guide을 원칙으로 한다. + - 단, 들여쓰기는 '2 spaces'가 아닌 '4 spaces'로 한다. + - indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. + - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. + - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다. + - 3항 연산자를 쓰지 않는다. + - 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라. + - JUnit 5와 AssertJ를 이용하여 정리한 기능 목록이 정상적으로 작동하는지 테스트 코드로 확인한다. + - 테스트 도구 사용법이 익숙하지 않다면 아래 문서를 참고하여 학습한 후 테스트를 구현한다. + - JUnit 5 User Guide + - AssertJ User Guide + - AssertJ Exception Assertions + - Guide to JUnit 5 Parameterized Test + +## 기능 목록 + +--- + +1. 컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다. +2. 사용자로부터 3자리의 수를 입력 받는다. + - 사용자가 잘못된 값을 입력할 경우 예외 발생 후 실행 종료 + - 3자리가 아닌 경우 + - 숫자가 아닌 경우 + - 0이 입력된 경우 + - 중복된 숫자가 입력된 경우 +3. 입력 값에 따라 힌트를 제공한다. + - 동일한 자리 - 동일한 수 → 스트라이크 + - 다른 자리 - 동일한 수 → 볼 + - 다른 수 → 낫싱 +4. 정답을 맞추면 게임을 종료한다. + - 3스트라이크인 경우 → 게임 종료 + - 3스트라이크가 아닌 경우 → 게임 진행 +5. 게임 종료 후, 게임을 다시 시작하거나 완전히 종료할 수 있다. + - 1 입력 → 게임 새로 시작 + - 2 입력 → 게임 완전히 종료 + - 사용자가 잘못된 값을 입력할 경우 예외 발생 후 실행 종료 + - 1, 2가 아닌 값을 입력할 경우 \ No newline at end of file From e353ba6c0ed8bac1dd2397b80e1fb0eb05408006 Mon Sep 17 00:00:00 2001 From: myeongheon Date: Tue, 7 May 2024 23:51:48 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat:=20=EC=BB=B4=ED=93=A8=ED=84=B0=203?= =?UTF-8?q?=EC=9E=90=EB=A6=AC=20=EC=88=AB=EC=9E=90=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/baseball/domain/NumberGenerator.java | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 src/main/java/baseball/domain/NumberGenerator.java diff --git a/src/main/java/baseball/domain/NumberGenerator.java b/src/main/java/baseball/domain/NumberGenerator.java new file mode 100644 index 00000000..7f964bd1 --- /dev/null +++ b/src/main/java/baseball/domain/NumberGenerator.java @@ -0,0 +1,17 @@ +package baseball.domain; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +public class NumberGenerator { + + public static String generateNumber() { + List numbers = new ArrayList<>(); + for (int i = 1; i <= 9; i++) { + numbers.add(i); + } + Collections.shuffle(numbers); + return "" + numbers.get(0) + numbers.get(1) + numbers.get(2); + } +} From 9165b11c6a20a51b5b1dbb5e09e9df165d2c340e Mon Sep 17 00:00:00 2001 From: myeongheon Date: Tue, 7 May 2024 23:53:40 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat:=20=EC=82=AC=EC=9A=A9=EC=9E=90=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=EA=B0=92=20=EA=B4=80=EB=A0=A8=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/baseball/domain/GameRules.java | 42 ++++++++++++++++++++ 1 file changed, 42 insertions(+) create mode 100644 src/main/java/baseball/domain/GameRules.java diff --git a/src/main/java/baseball/domain/GameRules.java b/src/main/java/baseball/domain/GameRules.java new file mode 100644 index 00000000..f297ddb5 --- /dev/null +++ b/src/main/java/baseball/domain/GameRules.java @@ -0,0 +1,42 @@ +package baseball.domain; + +public class GameRules { + public String generateHint(String secretNumber, String userGuess) { + int strike = 0; + int ball = 0; + for (int i = 0; i < 3; i++) { + if (userGuess.charAt(i) == secretNumber.charAt(i)) { + strike++; + } else if (secretNumber.contains(userGuess.substring(i, i+1))) { + ball++; + } + } + return createHintMessage(strike, ball); + } + + private String createHintMessage(int strike, int ball) { + String result = ""; + if (strike == 0 && ball == 0) { + result = "낫싱"; + } else { + if (ball > 0) { + result += ball + "볼 "; + } + if (strike > 0) { + result += strike + "스트라이크"; + } + } + return result.trim(); + } + + public Boolean decideRestart(String input) { + switch (input) { + case "1": + return true; + case "2": + return false; + default: + return null; + } + } +} From bbd60c69719740a075a6da22b621fb4a6bfd00e1 Mon Sep 17 00:00:00 2001 From: myeongheon Date: Tue, 7 May 2024 23:54:33 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat:=20=EC=9E=85=EB=A0=A5=EA=B0=92=20?= =?UTF-8?q?=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80=EC=82=AC=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/baseball/game/InputValidator.java | 32 +++++++++++++++++++ 1 file changed, 32 insertions(+) create mode 100644 src/main/java/baseball/game/InputValidator.java diff --git a/src/main/java/baseball/game/InputValidator.java b/src/main/java/baseball/game/InputValidator.java new file mode 100644 index 00000000..db5c63e6 --- /dev/null +++ b/src/main/java/baseball/game/InputValidator.java @@ -0,0 +1,32 @@ +package baseball.game; + +import java.util.HashSet; +import java.util.Set; + +public class InputValidator { + public static boolean isValidInput(String input) { + return isProperLength(input) && isAllDigits(input) && hasNoZero(input) && hasUniqueDigits(input); + } + + private static boolean isProperLength(String input) { + return input.length() == 3; + } + + private static boolean isAllDigits(String input) { + return input.chars().allMatch(Character::isDigit); + } + + private static boolean hasNoZero(String input) { + return !input.contains("0"); + } + + private static boolean hasUniqueDigits(String input) { + Set digits = new HashSet<>(); + for (char c : input.toCharArray()) { + if (!digits.add(c)) { + return false; + } + } + return true; + } +} From 442bb059f67b99afe13c6c8f5966390b52473605 Mon Sep 17 00:00:00 2001 From: myeongheon Date: Tue, 7 May 2024 23:55:18 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat:=20=EA=B2=8C=EC=9E=84=20=ED=99=94?= =?UTF-8?q?=EB=A9=B4=20=EA=B8=B0=EB=8A=A5=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/baseball/view/GameView.java | 33 +++++++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/main/java/baseball/view/GameView.java diff --git a/src/main/java/baseball/view/GameView.java b/src/main/java/baseball/view/GameView.java new file mode 100644 index 00000000..10400d8a --- /dev/null +++ b/src/main/java/baseball/view/GameView.java @@ -0,0 +1,33 @@ +package baseball.view; + +import java.util.Scanner; + +public class GameView { + private Scanner scanner; + + public GameView() { + this.scanner = new Scanner(System.in); + } + + public String promptForGuess() { + System.out.println("숫자를 입력해 주세요: "); + return scanner.nextLine(); + } + + public void displayResult(String result) { + System.out.println(result); + } + + public void displayGameEnd() { + System.out.println("3개의 숫자를 모두 맞히셨습니다! 게임 종료"); + } + + public String promptForRestart() { + System.out.println("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요."); + return scanner.nextLine(); + } + + public void displayError(String message) { + System.out.println(message); + } +} From 40b6fe938e0faa8be7a2863faa230959ea451852 Mon Sep 17 00:00:00 2001 From: myeongheon Date: Tue, 7 May 2024 23:55:50 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat:=20=EA=B2=8C=EC=9E=84=EC=A7=84?= =?UTF-8?q?=ED=96=89=20=EA=B8=B0=EB=8A=A5=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/baseball/game/Game.java | 63 +++++++++++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/main/java/baseball/game/Game.java diff --git a/src/main/java/baseball/game/Game.java b/src/main/java/baseball/game/Game.java new file mode 100644 index 00000000..4fc8ba5b --- /dev/null +++ b/src/main/java/baseball/game/Game.java @@ -0,0 +1,63 @@ +package baseball.game; + +import baseball.domain.GameRules; +import baseball.domain.NumberGenerator; +import baseball.view.GameView; + +public class Game { + private GameView gameView; + private NumberGenerator numberGenerator; + private GameRules gameRules; + private String computerNumber; + + public Game() { + this.gameView = new GameView(); + this.numberGenerator = new NumberGenerator(); + this.gameRules = new GameRules(); + this.computerNumber = numberGenerator.generateNumber(); + } + + public void startGame() { + boolean gameContinues = true; + try { + while (gameContinues) { + gameContinues = game(); + } + } catch (IllegalArgumentException e) { + gameView.displayError(e.getMessage()); + } + } + + private boolean game() { + String userGuess = gameView.promptForGuess(); + if (!InputValidator.isValidInput(userGuess)) { + throw new IllegalArgumentException("잘못된 값을 입력했습니다."); + } + String result = gameRules.generateHint(computerNumber, userGuess); + gameView.displayResult(result); + if (result.equals("3스트라이크")) { + gameView.displayGameEnd(); + return promptForRestart(); + } + return true; + } + + private boolean promptForRestart() { + while (true) { + String input = gameView.promptForRestart(); + Boolean decision = gameRules.decideRestart(input); + if (decision == null) { + throw new IllegalArgumentException("잘못된 값을 입력했습니다."); + } else { + if (decision) { + resetGame(); + } + return decision; + } + } + } + + private void resetGame() { + computerNumber = numberGenerator.generateNumber(); + } +} From 69caaed96b88fd8031cc55ed0d307b887493a606 Mon Sep 17 00:00:00 2001 From: myeongheon Date: Tue, 7 May 2024 23:56:12 +0900 Subject: [PATCH 07/10] =?UTF-8?q?feat:=20=EB=A9=94=EC=9D=B8=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=20=EA=B8=B0=EB=8A=A5=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/baseball/Application.java | 10 ++++++++++ 1 file changed, 10 insertions(+) create mode 100644 src/main/java/baseball/Application.java diff --git a/src/main/java/baseball/Application.java b/src/main/java/baseball/Application.java new file mode 100644 index 00000000..57aea45f --- /dev/null +++ b/src/main/java/baseball/Application.java @@ -0,0 +1,10 @@ +package baseball; + +import baseball.game.Game; + +public class Application { + public static void main(String[] args) { + Game game = new Game(); + game.startGame(); + } +} From e235003b8fba8fdd1abc8d6db1916f598f228955 Mon Sep 17 00:00:00 2001 From: myeongheon Date: Tue, 7 May 2024 23:57:14 +0900 Subject: [PATCH 08/10] =?UTF-8?q?feat:=20NumberGenerator=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../baseball/domain/NumberGeneratorTest.java | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 src/test/java/baseball/domain/NumberGeneratorTest.java diff --git a/src/test/java/baseball/domain/NumberGeneratorTest.java b/src/test/java/baseball/domain/NumberGeneratorTest.java new file mode 100644 index 00000000..fd27dbba --- /dev/null +++ b/src/test/java/baseball/domain/NumberGeneratorTest.java @@ -0,0 +1,18 @@ +package baseball.domain; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; + +class NumberGeneratorTest { + + @Test + @DisplayName("testGenerateNumber() : 컴퓨터는 1-9까지 서로 다른 임의의 수를 3개를 선택한다.") + public void testGenerateNumber() { + String number = NumberGenerator.generateNumber(); + assertThat(number).hasSize(3); + assertThat(number).matches("[1-9]+"); + assertThat(number.chars().distinct().count()).isEqualTo(3); + } + +} \ No newline at end of file From 1f91c8822850b52b4a091fa3411b7df543c38d35 Mon Sep 17 00:00:00 2001 From: myeongheon Date: Tue, 7 May 2024 23:57:53 +0900 Subject: [PATCH 09/10] =?UTF-8?q?feat:=20InputValidatorTest=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=EC=BD=94=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../baseball/game/InputValidatorTest.java | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 src/test/java/baseball/game/InputValidatorTest.java diff --git a/src/test/java/baseball/game/InputValidatorTest.java b/src/test/java/baseball/game/InputValidatorTest.java new file mode 100644 index 00000000..dd8e30d9 --- /dev/null +++ b/src/test/java/baseball/game/InputValidatorTest.java @@ -0,0 +1,33 @@ +package baseball.game; + +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; + +class InputValidatorTest { + + @Test + public void testIsValidInput_Valid() { + assertThat(InputValidator.isValidInput("123")).isTrue(); + } + + @Test + public void testIsValidInput_InvalidLength() { + assertThat(InputValidator.isValidInput("12")).isFalse(); + } + + @Test + public void testIsValidInput_ContainsNonDigit() { + assertThat(InputValidator.isValidInput("1a3")).isFalse(); + } + + @Test + public void testIsValidInput_ContainsZero() { + assertThat(InputValidator.isValidInput("103")).isFalse(); + } + + @Test + public void testIsValidInput_DuplicateDigits() { + assertThat(InputValidator.isValidInput("112")).isFalse(); + } + +} \ No newline at end of file From 6b73cc4dcc428cc787d745f346cdba11dbfe6eb2 Mon Sep 17 00:00:00 2001 From: myeongheon Date: Tue, 7 May 2024 23:58:16 +0900 Subject: [PATCH 10/10] =?UTF-8?q?feat:=20GameRulesTest=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/baseball/domain/GameRulesTest.java | 62 +++++++++++++++++++ 1 file changed, 62 insertions(+) create mode 100644 src/test/java/baseball/domain/GameRulesTest.java diff --git a/src/test/java/baseball/domain/GameRulesTest.java b/src/test/java/baseball/domain/GameRulesTest.java new file mode 100644 index 00000000..87556a77 --- /dev/null +++ b/src/test/java/baseball/domain/GameRulesTest.java @@ -0,0 +1,62 @@ +package baseball.domain; + +import org.junit.jupiter.api.Test; +import static org.assertj.core.api.Assertions.assertThat; + +class GameRulesTest { + + @Test + public void testGenerateHint_AllStrikes() { + GameRules rules = new GameRules(); + String hint = rules.generateHint("123", "123"); + assertThat(hint).isEqualTo("3스트라이크"); + } + + @Test + public void testGenerateHint_SomeBalls() { + GameRules rules = new GameRules(); + String hint = rules.generateHint("123", "231"); + assertThat(hint).isEqualTo("3볼"); + } + + @Test + public void testGenerateHint_Mixed() { + GameRules rules = new GameRules(); + String hint = rules.generateHint("123", "132"); + assertThat(hint).isEqualTo("2볼 1스트라이크"); + } + + @Test + public void testGenerateHint_Nothing() { + GameRules rules = new GameRules(); + String hint = rules.generateHint("123", "456"); + assertThat(hint).isEqualTo("낫싱"); + } + + @Test + public void testDecideRestart_ReturnsTrueFor1() { + GameRules rules = new GameRules(); + Boolean result = rules.decideRestart("1"); + assertThat(result).isTrue(); + } + + @Test + public void testDecideRestart_ReturnsFalseFor2() { + GameRules rules = new GameRules(); + Boolean result = rules.decideRestart("2"); + assertThat(result).isFalse(); + } + + @Test + public void testDecideRestart_ReturnsNullForOtherInputs() { + GameRules rules = new GameRules(); + Boolean result = rules.decideRestart("3"); + assertThat(result).isNull(); + + result = rules.decideRestart("hello"); + assertThat(result).isNull(); + + result = rules.decideRestart(""); + assertThat(result).isNull(); + } +} \ No newline at end of file