diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000000..6c01878138 --- /dev/null +++ b/.gitignore @@ -0,0 +1,32 @@ +HELP.md +.gradle +build/ +!gradle/wrapper/gradle-wrapper.jar +!**/src/main/** +!**/src/test/** + +### STS ### +.apt_generated +.classpath +.factorypath +.project +.settings +.springBeans +.sts4-cache + +### IntelliJ IDEA ### +.idea +*.iws +*.iml +*.ipr +out/ + +### NetBeans ### +/nbproject/private/ +/nbbuild/ +/dist/ +/nbdist/ +/.nb-gradle/ + +### VS Code ### +.vscode/ diff --git a/README.md b/README.md new file mode 100644 index 0000000000..b50d652cca --- /dev/null +++ b/README.md @@ -0,0 +1,155 @@ +# 미션 - 숫자 야구 + +## 🔍 진행 방식 + +- 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있다. +- 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다. +- 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다. + +## 📮 미션 제출 방법 + +- 미션 구현을 완료한 후 GitHub을 통해 제출해야 한다. + - GitHub을 활용한 제출 방법은 [프리코스 과제 제출](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 문서를 참고해 + 제출한다. +- GitHub에 미션을 제출한 후 [우아한테크코스 지원](https://apply.techcourse.co.kr) 사이트에 접속하여 프리코스 과제를 제출한다. + - 자세한 방법은 [제출 가이드](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse#제출-가이드) 참고 + - **Pull Request만 보내고 지원 플랫폼에서 과제를 제출하지 않으면 최종 제출하지 않은 것으로 처리되니 주의한다.** + +## 🚨 과제 제출 전 체크 리스트 - 0점 방지 + +- 기능 구현을 모두 정상적으로 했더라도 **요구 사항에 명시된 출력값 형식을 지키지 않을 경우 0점으로 처리**한다. +- 기능 구현을 완료한 뒤 아래 가이드에 따라 테스트를 실행했을 때 모든 테스트가 성공하는지 확인한다. +- **테스트가 실패할 경우 0점으로 처리**되므로, 반드시 확인 후 제출한다. + +### 테스트 실행 가이드 + +- 터미널에서 `java -version`을 실행하여 Java 버전이 11인지 확인한다. 또는 Eclipse 또는 IntelliJ IDEA와 같은 IDE에서 Java 11로 실행되는지 확인한다. +- 터미널에서 Mac 또는 Linux 사용자의 경우 `./gradlew clean test` 명령을 실행하고, + Windows 사용자의 경우 `gradlew.bat clean test` 명령을 실행할 때 모든 테스트가 아래와 같이 통과하는지 확인한다. + +``` +BUILD SUCCESSFUL in 0s +``` + +--- + +## 🚀 기능 요구 사항 + +기본적으로 1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임이다. + +- 같은 수가 같은 자리에 있으면 스트라이크, 다른 자리에 있으면 볼, 같은 수가 전혀 없으면 낫싱이란 힌트를 얻고, 그 힌트를 이용해서 먼저 상대방(컴퓨터)의 수를 맞추면 승리한다. + - 예) 상대방(컴퓨터)의 수가 425일 때 + - 123을 제시한 경우 : 1스트라이크 + - 456을 제시한 경우 : 1볼 1스트라이크 + - 789를 제시한 경우 : 낫싱 +- 위 숫자 야구 게임에서 상대방의 역할을 컴퓨터가 한다. 컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다. 게임 플레이어는 컴퓨터가 생각하고 있는 서로 다른 3개의 숫자를 입력하고, 컴퓨터는 입력한 숫자에 대한 + 결과를 출력한다. +- 이 같은 과정을 반복해 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임이 종료된다. +- 게임을 종료한 후 게임을 다시 시작하거나 완전히 종료할 수 있다. +- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시킨 후 애플리케이션은 종료되어야 한다. + +### 입출력 요구 사항 + +#### 입력 + +- 서로 다른 3자리의 수 +- 게임이 끝난 경우 재시작/종료를 구분하는 1과 2 중 하나의 수 + +#### 출력 + +- 입력한 수에 대한 결과를 볼, 스트라이크 개수로 표시 + +``` +1볼 1스트라이크 +``` + +- 하나도 없는 경우 + +``` +낫싱 +``` + +- 3개의 숫자를 모두 맞힐 경우 + +``` +3스트라이크 +3개의 숫자를 모두 맞히셨습니다! 게임 종료 +``` + +- 게임 시작 문구 출력 + +``` +숫자 야구 게임을 시작합니다. +``` + +#### 실행 결과 예시 + +``` +숫자 야구 게임을 시작합니다. +숫자를 입력해주세요 : 123 +1볼 1스트라이크 +숫자를 입력해주세요 : 145 +1볼 +숫자를 입력해주세요 : 671 +2볼 +숫자를 입력해주세요 : 216 +1스트라이크 +숫자를 입력해주세요 : 713 +3스트라이크 +3개의 숫자를 모두 맞히셨습니다! 게임 종료 +게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요. +1 +숫자를 입력해주세요 : 123 +1볼 +... +``` + +--- + +## 🎯 프로그래밍 요구 사항 + +- JDK 11 버전에서 실행 가능해야 한다. **JDK 11에서 정상적으로 동작하지 않을 경우 0점 처리한다.** +- 프로그램 실행의 시작점은 `Application`의 `main()`이다. +- `build.gradle` 파일을 변경할 수 없고, 외부 라이브러리를 사용하지 않는다. +- [Java 코드 컨벤션](https://github.com/woowacourse/woowacourse-docs/tree/master/styleguide/java) 가이드를 준수하며 프로그래밍한다. +- 프로그램 종료 시 `System.exit()`를 호출하지 않는다. +- 프로그램 구현이 완료되면 `ApplicationTest`의 모든 테스트가 성공해야 한다. **테스트가 실패할 경우 0점 처리한다.** +- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 이름을 수정하거나 이동하지 않는다. + +### 추가된 요구 사항 + +- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. + - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. + - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다. +- 3항 연산자를 쓰지 않는다. +- 함수(또는 메서드)가 한 가지 일만 하도록 최대한 작게 만들어라. +- JUnit 5와 AssertJ를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다. + - 테스트 도구 사용법이 익숙하지 않다면 `test/java/study`를 참고하여 학습한 후 테스트를 구현한다. + +### 라이브러리 + +- `camp.nextstep.edu.missionutils`에서 제공하는 `Randoms` 및 `Console` API를 사용하여 구현해야 한다. + - Random 값 추출은 `camp.nextstep.edu.missionutils.Randoms`의 `pickNumberInRange()`를 활용한다. + - 사용자가 입력하는 값은 `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 활용한다. + +#### 사용 예시 + +```java +List computer = new ArrayList<>(); +while (computer.size() < 3) { + int randomNumber = Randoms.pickNumberInRange(1, 9); + if (!computer.contains(randomNumber)) { + computer.add(randomNumber); + } +} +``` + +--- + +## ✏️ 과제 진행 요구 사항 + +- 미션은 [java-baseball](https://github.com/woowacourse-precourse/java-baseball) 저장소를 Fork & Clone해 시작한다. +- **기능을 구현하기 전 `docs/README.md`에 구현할 기능 목록을 정리**해 추가한다. +- **Git의 커밋 단위는 앞 단계에서 `docs/README.md`에 정리한 기능 목록 단위**로 추가한다. + - [커밋 메시지 컨벤션](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) 가이드를 참고해 커밋 메시지를 작성한다. +- 과제 진행 및 제출 방법은 [프리코스 과제 제출](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 문서를 참고한다. diff --git a/build.gradle b/build.gradle new file mode 100644 index 0000000000..cf1e54f329 --- /dev/null +++ b/build.gradle @@ -0,0 +1,25 @@ +plugins { + id 'java' +} + +group 'camp.nextstep.edu' +version '1.0-SNAPSHOT' + +repositories { + mavenCentral() + maven { url 'https://jitpack.io' } +} + +dependencies { + implementation 'com.github.woowacourse-projects:mission-utils:1.0.0' +} + +java { + toolchain { + languageVersion = JavaLanguageVersion.of(11) + } +} + +test { + useJUnitPlatform() +} diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..e69de29bb2 diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 0000000000..16e868eed6 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Dfile.encoding=UTF-8 +org.gradle.console=plain diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000..e708b1c023 Binary files /dev/null and b/gradle/wrapper/gradle-wrapper.jar differ diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000000..28ff446a21 --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,5 @@ +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-6.8.1-bin.zip +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists diff --git a/gradlew b/gradlew new file mode 100755 index 0000000000..4f906e0c81 --- /dev/null +++ b/gradlew @@ -0,0 +1,185 @@ +#!/usr/bin/env sh + +# +# Copyright 2015 the original author or authors. +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# https://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >/dev/null +APP_HOME="`pwd -P`" +cd "$SAVED" >/dev/null + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"' + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn () { + echo "$*" +} + +die () { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +nonstop=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; + NONSTOP* ) + nonstop=true + ;; +esac + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin or MSYS, switch paths to Windows format before running java +if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + JAVACMD=`cygpath --unix "$JAVACMD"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=`expr $i + 1` + done + case $i in + 0) set -- ;; + 1) set -- "$args0" ;; + 2) set -- "$args0" "$args1" ;; + 3) set -- "$args0" "$args1" "$args2" ;; + 4) set -- "$args0" "$args1" "$args2" "$args3" ;; + 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Escape application args +save () { + for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done + echo " " +} +APP_ARGS=`save "$@"` + +# Collect all arguments for the java command, following the shell quoting and substitution rules +eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS" + +exec "$JAVACMD" "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 0000000000..107acd32c4 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,89 @@ +@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=. +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%" == "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%"=="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! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/settings.gradle b/settings.gradle new file mode 100644 index 0000000000..c1ba10b992 --- /dev/null +++ b/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'java-baseball' diff --git a/src/main/java/baseball/Application.java b/src/main/java/baseball/Application.java new file mode 100644 index 0000000000..dd95a34214 --- /dev/null +++ b/src/main/java/baseball/Application.java @@ -0,0 +1,7 @@ +package baseball; + +public class Application { + public static void main(String[] args) { + // TODO: 프로그램 구현 + } +} diff --git a/src/test/java/baseball/ApplicationTest.java b/src/test/java/baseball/ApplicationTest.java new file mode 100644 index 0000000000..3fa29fa67b --- /dev/null +++ b/src/test/java/baseball/ApplicationTest.java @@ -0,0 +1,35 @@ +package baseball; + +import camp.nextstep.edu.missionutils.test.NsTest; +import org.junit.jupiter.api.Test; + +import static camp.nextstep.edu.missionutils.test.Assertions.assertRandomNumberInRangeTest; +import static camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest; +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class ApplicationTest extends NsTest { + @Test + void 게임종료_후_재시작() { + assertRandomNumberInRangeTest( + () -> { + run("246", "135", "1", "597", "589", "2"); + assertThat(output()).contains("낫싱", "3스트라이크", "1볼 1스트라이크", "3스트라이크", "게임 종료"); + }, + 1, 3, 5, 5, 8, 9 + ); + } + + @Test + void 예외_테스트() { + assertSimpleTest(() -> + assertThatThrownBy(() -> runException("1234")) + .isInstanceOf(IllegalArgumentException.class) + ); + } + + @Override + public void runMain() { + Application.main(new String[]{}); + } +} diff --git a/src/test/java/study/StringTest.java b/src/test/java/study/StringTest.java new file mode 100644 index 0000000000..462ab1c4a4 --- /dev/null +++ b/src/test/java/study/StringTest.java @@ -0,0 +1,50 @@ +package study; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.*; + +public class StringTest { + + @Test + void split_메서드로_주어진_값을_구분() { + String input = "1,2"; + String[] result = input.split(","); + + assertThat(result).contains("2", "1"); + assertThat(result).containsExactly("1", "2"); + } + + @Test + void split_메서드_사용시_구분자가_포함되지_않은_경우_값을_그대로_반환() { + String input = "1"; + String[] result = input.split(","); + + assertThat(result).contains("1"); + } + + @Test + void substring_메서드로_특정_구간_값을_반환() { + String input = "(1,2)"; + String result = input.substring(1, 4); + + assertThat(result).isEqualTo("1,2"); + } + + @Test + void charAt_메서드로_특정_위치의_문자_찾기() { + String input = "abc"; + char charAtElement = input.charAt(0); + assertThat(charAtElement).isEqualTo('a'); + } + + @Test + void charAt_메서드_사용시_문자열의_길이보다_큰_숫자_위치의_문자를_찾을_때_예외_발생() { + String input = "abc"; + + assertThatThrownBy(() -> input.charAt(5)) + .isInstanceOf(StringIndexOutOfBoundsException.class) + .hasMessageContaining("String index out of range: 5"); + } + +}