diff --git a/.DS_Store b/.DS_Store index 7f93ef9..bdbf73e 100644 Binary files a/.DS_Store and b/.DS_Store differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 37aef8d..3499ded 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.5-bin.zip networkTimeout=10000 zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/gradlew.bat b/gradlew.bat index 93e3f59..6689b85 100644 --- a/gradlew.bat +++ b/gradlew.bat @@ -1,92 +1,92 @@ -@rem -@rem Copyright 2015 the original author or authors. -@rem -@rem Licensed under the Apache License, Version 2.0 (the "License"); -@rem you may not use this file except in compliance with the License. -@rem You may obtain a copy of the License at -@rem -@rem https://www.apache.org/licenses/LICENSE-2.0 -@rem -@rem Unless required by applicable law or agreed to in writing, software -@rem distributed under the License is distributed on an "AS IS" BASIS, -@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -@rem See the License for the specific language governing permissions and -@rem limitations under the License. -@rem - -@if "%DEBUG%"=="" @echo off -@rem ########################################################################## -@rem -@rem Gradle startup script for Windows -@rem -@rem ########################################################################## - -@rem Set local scope for the variables with windows NT shell -if "%OS%"=="Windows_NT" setlocal - -set DIRNAME=%~dp0 -if "%DIRNAME%"=="" set DIRNAME=. -@rem This is normally unused -set APP_BASE_NAME=%~n0 -set APP_HOME=%DIRNAME% - -@rem Resolve any "." and ".." in APP_HOME to make it shorter. -for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi - -@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. -set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" - -@rem Find java.exe -if defined JAVA_HOME goto findJavaFromJavaHome - -set JAVA_EXE=java.exe -%JAVA_EXE% -version >NUL 2>&1 -if %ERRORLEVEL% equ 0 goto execute - -echo. -echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:findJavaFromJavaHome -set JAVA_HOME=%JAVA_HOME:"=% -set JAVA_EXE=%JAVA_HOME%/bin/java.exe - -if exist "%JAVA_EXE%" goto execute - -echo. -echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% -echo. -echo Please set the JAVA_HOME variable in your environment to match the -echo location of your Java installation. - -goto fail - -:execute -@rem Setup the command line - -set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar - - -@rem Execute Gradle -"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* - -:end -@rem End local scope for the variables with windows NT shell -if %ERRORLEVEL% equ 0 goto mainEnd - -:fail -rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of -rem the _cmd.exe /c_ return code! -set EXIT_CODE=%ERRORLEVEL% -if %EXIT_CODE% equ 0 set EXIT_CODE=1 -if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% -exit /b %EXIT_CODE% - -:mainEnd -if "%OS%"=="Windows_NT" endlocal - -:omega +@rem +@rem Copyright 2015 the original author or authors. +@rem +@rem Licensed under the Apache License, Version 2.0 (the "License"); +@rem you may not use this file except in compliance with the License. +@rem You may obtain a copy of the License at +@rem +@rem https://www.apache.org/licenses/LICENSE-2.0 +@rem +@rem Unless required by applicable law or agreed to in writing, software +@rem distributed under the License is distributed on an "AS IS" BASIS, +@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +@rem See the License for the specific language governing permissions and +@rem limitations under the License. +@rem + +@if "%DEBUG%"=="" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +set DIRNAME=%~dp0 +if "%DIRNAME%"=="" set DIRNAME=. +@rem This is normally unused +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Resolve any "." and ".." in APP_HOME to make it shorter. +for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m" + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if %ERRORLEVEL% equ 0 goto execute + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto execute + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %* + +:end +@rem End local scope for the variables with windows NT shell +if %ERRORLEVEL% equ 0 goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +set EXIT_CODE=%ERRORLEVEL% +if %EXIT_CODE% equ 0 set EXIT_CODE=1 +if not ""=="%GRADLE_EXIT_CONSOLE%" exit %EXIT_CODE% +exit /b %EXIT_CODE% + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/separate_README.md b/separate_README.md new file mode 100644 index 0000000..c0c98df --- /dev/null +++ b/separate_README.md @@ -0,0 +1,23 @@ +# 기능요구사항 +- Exception + +- GameController + - 서로 다른 3 랜덤 숫자 생성 + - 사용자 랜덤 숫자 추측 입력 + - 추측 결과 출력 + - 게임 재시작 / 종료 여부 입력 + - 게임 재시작 / 종료 + +- Number + - 숫자 유효성 검사 + - 추측 결과 반환 + +- InputView + - 서로 다른 3자리의 수 + - 게임 재시작 / 종료 (1 / 2) + +- OutputView + - 게임 시작 문구 + - 입력한 숫자에 대한 결과 출력 + - 볼, 스트라이크 개수로 표시 + - 3개의 숫자 모두 맞힐 경우 게임 종료 diff --git a/src/main/java/baseball/Application.java b/src/main/java/baseball/Application.java index dd95a34..8fefe15 100644 --- a/src/main/java/baseball/Application.java +++ b/src/main/java/baseball/Application.java @@ -1,7 +1,14 @@ package baseball; +import baseball.game.controller.GameController; +import baseball.view.Inputview; +import baseball.view.OutputView; + public class Application { public static void main(String[] args) { - // TODO: 프로그램 구현 + OutputView outputView = new OutputView(); + Inputview inputview = new Inputview(); + GameController gameController = new GameController(outputView, inputview); + gameController.play(); } } diff --git a/src/main/java/baseball/baseNumber/BaseNumber.java b/src/main/java/baseball/baseNumber/BaseNumber.java new file mode 100644 index 0000000..28b3946 --- /dev/null +++ b/src/main/java/baseball/baseNumber/BaseNumber.java @@ -0,0 +1,66 @@ +package baseball.baseNumber; + +import baseball.game.controller.constant.Rules; +import baseball.guessNumber.GuessNumber; +import baseball.guessResult.GuessResult; +import camp.nextstep.edu.missionutils.Randoms; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class BaseNumber { + private final List numbers; + + public BaseNumber(List numbers) { + this.numbers = new ArrayList<>(numbers); + } + + public static BaseNumber from(Set numbers) { + validateCount(numbers); + return new BaseNumber(numbers.stream().toList()); + } + + private static void validateCount(Set numbers) { + if (numbers.size() != Rules.BASE_NUMBER_COUNT) { + throw new IllegalArgumentException(); + } + } + + + public GuessResult match(GuessNumber guessNumber) { + int strike = checkStrike(guessNumber); + int temporaryBall = checkTemporaryBall(guessNumber); + return GuessResult.from(strike, temporaryBall - strike); + } + + private int checkStrike(GuessNumber guessNumber) { + int strike = 0; + for (int i = 0; i < Rules.BASE_NUMBER_COUNT; ++i) { + int singleGuessNumber = guessNumber.numbers.get(i); + if (isStrike(singleGuessNumber, numbers.get(i))) { + strike++; + } + } + return strike; + } + + private int checkTemporaryBall(GuessNumber guessNumber) { + int ball = 0; + for (int i = 0; i < Rules.BASE_NUMBER_COUNT; ++i) { + if (isBall(guessNumber.numbers.get(i))) { + ball++; + } + } + return ball; + } + + private boolean isStrike(int guessNumber, int baseNumber) { + return guessNumber == baseNumber; + } + + private boolean isBall(int singleGuessNumber) { + return numbers.contains(singleGuessNumber); + } +} diff --git a/src/main/java/baseball/game/controller/GameController.java b/src/main/java/baseball/game/controller/GameController.java new file mode 100644 index 0000000..8306f89 --- /dev/null +++ b/src/main/java/baseball/game/controller/GameController.java @@ -0,0 +1,109 @@ +package baseball.game.controller; + +import baseball.baseNumber.BaseNumber; +import baseball.game.controller.constant.InputMessage; +import baseball.game.controller.constant.OutputMessage; +import baseball.game.controller.constant.Rules; +import baseball.guessNumber.GuessNumber; +import baseball.guessResult.GuessResult; +import baseball.view.Inputview; +import baseball.view.OutputView; +import camp.nextstep.edu.missionutils.Randoms; + +import java.util.ArrayList; +import java.util.HashSet; +import java.util.List; +import java.util.Set; + +public class GameController { + + private final OutputView outputView; + private final Inputview inputview; + + public GameController(OutputView outputView, Inputview inputview) { + this.outputView = outputView; + this.inputview = inputview; + } + + public void play () { + do { + playSingle(); + } while(UserWantsToPlayMore()); + } + + private void playSingle() { + outputView.print(OutputMessage.GAME_START); + BaseNumber number = BaseNumber.from(createRandomDistictNumbers()); + GuessUntilFullStrike(number); + outputView.print(OutputMessage.GAME_END); + } + + private Set createRandomDistictNumbers() { + Set distinctNumbers = new HashSet<>(); + while(distinctNumbers.size() < Rules.BASE_NUMBER_COUNT) { + int randomNumber = Randoms.pickNumberInRange(1, 9); + distinctNumbers.add(randomNumber); + } + return distinctNumbers; + } + + private void GuessUntilFullStrike(BaseNumber number) { + GuessResult guessResult; + do { + String input = inputview.promptUserInput(InputMessage.GUESS_BASE_NUMBER); + List inputNumbers = validateGuessNumber(input); + GuessNumber guessNumber = GuessNumber.from(inputNumbers); + guessResult = number.match(guessNumber); + outputView.print(guessResult); + } while(!guessResult.fullStrike()); + } + + private List validateGuessNumber(String input) { + List numbers = new ArrayList<>(); + validateLength(input); + addNumbers(numbers, input); + validateDistinct(numbers); + return numbers; + } + + private void validateDistinct(List numbers) { + Set distictNumber = new HashSet<>(numbers); + if (numbers.size() != distictNumber.size()) { + throw new IllegalArgumentException(); + } + } + + private void addNumbers(List numbers, String input) { + for (int i = 0; i < Rules.BASE_NUMBER_COUNT; ++i) { + try { + int number = Integer.parseInt(input.substring(i, i+1)); + numbers.add(number); + } catch (NumberFormatException e) { + throw new IllegalArgumentException(); + } + } + } + + private void validateLength(String input) { + if (input.length() != 3) { + throw new IllegalArgumentException(); + } + } + + private boolean UserWantsToPlayMore() { + String input = inputview.promptUserInput(InputMessage.GAME_CONTINUE); + boolean gameContinue = validateGameContinue(input); + return gameContinue; + } + + private boolean validateGameContinue(String input) { + if (input.equals("1")) { + return true; + } + if (input.equals("2")) { + return false; + } + throw new IllegalArgumentException(); + } + +} diff --git a/src/main/java/baseball/game/controller/constant/InputMessage.java b/src/main/java/baseball/game/controller/constant/InputMessage.java new file mode 100644 index 0000000..f1a49cb --- /dev/null +++ b/src/main/java/baseball/game/controller/constant/InputMessage.java @@ -0,0 +1,6 @@ +package baseball.game.controller.constant; + +public class InputMessage { + public final static String GUESS_BASE_NUMBER = "숫자를 입력해주세요 : "; + public static final String GAME_CONTINUE = "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요." + System.lineSeparator(); +} diff --git a/src/main/java/baseball/game/controller/constant/OutputMessage.java b/src/main/java/baseball/game/controller/constant/OutputMessage.java new file mode 100644 index 0000000..012032e --- /dev/null +++ b/src/main/java/baseball/game/controller/constant/OutputMessage.java @@ -0,0 +1,6 @@ +package baseball.game.controller.constant; + +public class OutputMessage { + public final static String GAME_START = "숫자 야구 게임을 시작합니다."; + public final static String GAME_END = Rules.BASE_NUMBER_COUNT + "개의 숫자를 모두 맞히셨습니다! 게임 종료"; +} diff --git a/src/main/java/baseball/game/controller/constant/Rules.java b/src/main/java/baseball/game/controller/constant/Rules.java new file mode 100644 index 0000000..e9b3c5f --- /dev/null +++ b/src/main/java/baseball/game/controller/constant/Rules.java @@ -0,0 +1,7 @@ +package baseball.game.controller.constant; + +public class Rules { + public final static int MINIMUM_BASE_NUMBER = 1; + public final static int MAXIMUM_BASE_NUMBER = 9; + public final static int BASE_NUMBER_COUNT = 3; +} diff --git a/src/main/java/baseball/guessNumber/GuessNumber.java b/src/main/java/baseball/guessNumber/GuessNumber.java new file mode 100644 index 0000000..d076ca8 --- /dev/null +++ b/src/main/java/baseball/guessNumber/GuessNumber.java @@ -0,0 +1,18 @@ +package baseball.guessNumber; + +import baseball.game.controller.constant.Rules; + +import java.util.ArrayList; +import java.util.List; + +public class GuessNumber { + public List numbers; + + public GuessNumber(List numbers) { + this.numbers = new ArrayList<>(numbers); + } + + public static GuessNumber from(List numbers) { + return new GuessNumber(numbers); + } +} diff --git a/src/main/java/baseball/guessResult/GuessResult.java b/src/main/java/baseball/guessResult/GuessResult.java new file mode 100644 index 0000000..608e8dc --- /dev/null +++ b/src/main/java/baseball/guessResult/GuessResult.java @@ -0,0 +1,55 @@ +package baseball.guessResult; + +import baseball.game.controller.constant.Rules; + +public class GuessResult { + private final int strike; + private final int ball; + + public GuessResult(int strike, int ball) { + this.strike = strike; + this.ball = ball; + } + + public static GuessResult from(int strike, int ball) { + return new GuessResult(strike, ball); + } + + @Override + public String toString() { + StringBuilder stringBuilder = new StringBuilder(); + if (nothing(stringBuilder)){ + return stringBuilder.toString(); + } + appendBall(stringBuilder); + appendStrike(stringBuilder); + return stringBuilder.toString(); + } + + private boolean nothing(StringBuilder stringBuilder) { + if (ball + strike == 0) { + stringBuilder.append("낫싱"); + return true; + } + return false; + } + + private void appendBall(StringBuilder stringBuilder) { + if (ball > 0) { + stringBuilder.append(ball); + stringBuilder.append("볼"); + stringBuilder.append(" "); + } + } + + private void appendStrike(StringBuilder stringBuilder) { + if (strike > 0) { + stringBuilder.append(strike); + stringBuilder.append("스트라이크"); + } + } + + public boolean fullStrike() { + return strike == Rules.BASE_NUMBER_COUNT; + } +} diff --git a/src/main/java/baseball/view/Inputview.java b/src/main/java/baseball/view/Inputview.java new file mode 100644 index 0000000..7285b7b --- /dev/null +++ b/src/main/java/baseball/view/Inputview.java @@ -0,0 +1,20 @@ +package baseball.view; + +import static camp.nextstep.edu.missionutils.Console.readLine; + +public class Inputview { + + public String promptUserInput(String message) { + writeSentence(message); + return readLine(); + } + + private void writeSentence(String message) { + System.out.print(message); + } + + private void writeLine(String message) { + System.out.println(message); + } + +} diff --git a/src/main/java/baseball/view/OutputView.java b/src/main/java/baseball/view/OutputView.java new file mode 100644 index 0000000..ab02aa7 --- /dev/null +++ b/src/main/java/baseball/view/OutputView.java @@ -0,0 +1,18 @@ +package baseball.view; + +import baseball.guessResult.GuessResult; + +public class OutputView { + + public void print(String message) { + writeLine(message); + } + + private void writeLine(String message) { + System.out.println(message); + } + + public void print(GuessResult guessResult) { + System.out.println(guessResult); + } +} diff --git a/src/test/java/baseball/baseNumber/BaseNumberTest.java b/src/test/java/baseball/baseNumber/BaseNumberTest.java new file mode 100644 index 0000000..f44e386 --- /dev/null +++ b/src/test/java/baseball/baseNumber/BaseNumberTest.java @@ -0,0 +1,56 @@ +package baseball.baseNumber; + +import baseball.guessNumber.GuessNumber; +import baseball.guessResult.GuessResult; + +import org.junit.jupiter.api.Test; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class BaseNumberTest { + + @Test + void from_베이스숫자갯수_예외처리() { + Set randomDistinctNumbers = new HashSet<>(List.of(1, 2, 3, 4)); + assertThatThrownBy( + ()-> { + BaseNumber baseNumber = BaseNumber.from(randomDistinctNumbers); + } + ).isInstanceOf(IllegalArgumentException.class); + } + + @Test + void match_3스트라이크() { + Set randomDistinctNumbers = new HashSet<>(List.of(1, 2, 3)); + BaseNumber baseNumber = BaseNumber.from(randomDistinctNumbers); + GuessNumber guessNumber = GuessNumber.from(randomDistinctNumbers.stream().toList()); + GuessResult guessResult = baseNumber.match(guessNumber); + + assertThat(guessResult.fullStrike()).isTrue(); + assertThat(guessResult.toString().contains("3스트라이크")).isTrue(); + } + + @Test + void match_낫싱() { + Set randomDistinctNumbers = new HashSet<>(List.of(1, 2, 3)); + BaseNumber baseNumber = BaseNumber.from(randomDistinctNumbers); + GuessNumber guessNumber = GuessNumber.from(List.of(4,5,6)); + GuessResult guessResult = baseNumber.match(guessNumber); + + assertThat(guessResult.toString().contains("낫싱")).isTrue(); + } + + @Test + void match_1볼_1스트라이크() { + Set randomDistinctNumbers = new HashSet<>(List.of(1, 2, 3)); + BaseNumber baseNumber = BaseNumber.from(randomDistinctNumbers); + GuessNumber guessNumber = GuessNumber.from(List.of(1,5,2)); + GuessResult guessResult = baseNumber.match(guessNumber); + + assertThat(guessResult.toString().contains("1볼 1스트라이크")).isTrue(); + } +} \ No newline at end of file