diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..23baf58 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# 디폴트 무시된 파일 +/shelf/ +/workspace.xml diff --git a/.idea/java-lotto-game.iml b/.idea/java-lotto-game.iml new file mode 100644 index 0000000..d6ebd48 --- /dev/null +++ b/.idea/java-lotto-game.iml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..9cc5498 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..a0fab7f --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 0000000..797acea --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,10 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/amaran-th/.gitignore b/amaran-th/.gitignore new file mode 100644 index 0000000..6c01878 --- /dev/null +++ b/amaran-th/.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/amaran-th/README.md b/amaran-th/README.md new file mode 100644 index 0000000..51335a9 --- /dev/null +++ b/amaran-th/README.md @@ -0,0 +1,227 @@ +# 미션 - 로또 + +## 🔍 진행 방식 + +- 미션은 **기능 요구 사항, 프로그래밍 요구 사항, 과제 진행 요구 사항** 세 가지로 구성되어 있다. +- 세 개의 요구 사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다. +- 기능 요구 사항에 기재되지 않은 내용은 스스로 판단하여 구현한다. + +## 📮 미션 제출 방법 + +- 미션 구현을 완료한 후 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~45까지이다. +- 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다. +- 당첨 번호 추첨 시 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑는다. +- 당첨은 1등부터 5등까지 있다. 당첨 기준과 금액은 아래와 같다. + - 1등: 6개 번호 일치 / 2,000,000,000원 + - 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원 + - 3등: 5개 번호 일치 / 1,500,000원 + - 4등: 4개 번호 일치 / 50,000원 + - 5등: 3개 번호 일치 / 5,000원 +``` + +- 로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 한다. +- 로또 1장의 가격은 1,000원이다. +- 당첨 번호와 보너스 번호를 입력받는다. +- 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다. +- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`를 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 종료한다. + +### 입출력 요구 사항 + +#### 입력 + +- 로또 구입 금액을 입력 받는다. 구입 금액은 1,000원 단위로 입력 받으며 1,000원으로 나누어 떨어지지 않는 경우 예외 처리한다. + +``` +14000 +``` + +- 당첨 번호를 입력 받는다. 번호는 쉼표(,)를 기준으로 구분한다. + +``` +1,2,3,4,5,6 +``` + +- 보너스 번호를 입력 받는다. + +``` +7 +``` + +#### 출력 + +- 발행한 로또 수량 및 번호를 출력한다. 로또 번호는 오름차순으로 정렬하여 보여준다. + +``` +8개를 구매했습니다. +[8, 21, 23, 41, 42, 43] +[3, 5, 11, 16, 32, 38] +[7, 11, 16, 35, 36, 44] +[1, 8, 11, 31, 41, 42] +[13, 14, 16, 38, 42, 45] +[7, 11, 30, 40, 42, 43] +[2, 13, 22, 32, 38, 45] +[1, 3, 5, 14, 22, 45] +``` + +- 당첨 내역을 출력한다. + +``` +3개 일치 (5,000원) - 1개 +4개 일치 (50,000원) - 0개 +5개 일치 (1,500,000원) - 0개 +5개 일치, 보너스 볼 일치 (30,000,000원) - 0개 +6개 일치 (2,000,000,000원) - 0개 +``` + +- 수익률은 소수점 둘째 자리에서 반올림한다. (ex. 100.0%, 51.5%, 1,000,000.0%) + +``` +총 수익률은 62.5%입니다. +``` + +- 예외 상황 시 에러 문구를 출력해야 한다. 단, 에러 문구는 "[ERROR]"로 시작해야 한다. + +``` +[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다. +``` + +#### 실행 결과 예시 + +``` +구입금액을 입력해 주세요. +8000 + +8개를 구매했습니다. +[8, 21, 23, 41, 42, 43] +[3, 5, 11, 16, 32, 38] +[7, 11, 16, 35, 36, 44] +[1, 8, 11, 31, 41, 42] +[13, 14, 16, 38, 42, 45] +[7, 11, 30, 40, 42, 43] +[2, 13, 22, 32, 38, 45] +[1, 3, 5, 14, 22, 45] + +당첨 번호를 입력해 주세요. +1,2,3,4,5,6 + +보너스 번호를 입력해 주세요. +7 + +당첨 통계 +--- +3개 일치 (5,000원) - 1개 +4개 일치 (50,000원) - 0개 +5개 일치 (1,500,000원) - 0개 +5개 일치, 보너스 볼 일치 (30,000,000원) - 0개 +6개 일치 (2,000,000,000원) - 0개 +총 수익률은 62.5%입니다. +``` + +--- + +## 🎯 프로그래밍 요구 사항 + +- 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를 이용하여 본인이 정리한 기능 목록이 정상 동작함을 테스트 코드로 확인한다. + +### 추가된 요구 사항 + +- 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다. + - 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다. +- else 예약어를 쓰지 않는다. + - 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다. + - else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 switch/case도 허용하지 않는다. +- Java Enum을 적용한다. +- 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다. + - 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리해 구현한다. + - 단위 테스트 작성이 익숙하지 않다면 `test/java/lotto/LottoTest`를 참고하여 학습한 후 테스트를 구현한다. + +### 라이브러리 + +- [`camp.nextstep.edu.missionutils`](https://github.com/woowacourse-projects/mission-utils)에서 제공하는 `Randoms` 및 `Console` API를 사용하여 구현해야 한다. + - Random 값 추출은 `camp.nextstep.edu.missionutils.Randoms`의 `pickUniqueNumbersInRange()`를 활용한다. + - 사용자가 입력하는 값은 `camp.nextstep.edu.missionutils.Console`의 `readLine()`을 활용한다. + +#### 사용 예시 + +```java +List numbers = Randoms.pickUniqueNumbersInRange(1, 45, 6); +``` + +### Lotto 클래스 + +- 제공된 `Lotto` 클래스를 활용해 구현해야 한다. +- `Lotto`에 매개 변수가 없는 생성자를 추가할 수 없다. +- `numbers`의 접근 제어자인 private을 변경할 수 없다. +- `Lotto`에 필드(인스턴스 변수)를 추가할 수 없다. +- `Lotto`의 패키지 변경은 가능하다. + +```java +public class Lotto { + private final List numbers; + + public Lotto(List numbers) { + validate(numbers); + this.numbers = numbers; + } + + private void validate(List numbers) { + if (numbers.size() != 6) { + throw new IllegalArgumentException(); + } + } + + // TODO: 추가 기능 구현 +} +``` + +--- + +## ✏️ 과제 진행 요구 사항 + +- 미션은 [java-lotto](https://github.com/woowacourse-precourse/java-lotto) 저장소를 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/amaran-th/build.gradle b/amaran-th/build.gradle new file mode 100644 index 0000000..9d087b1 --- /dev/null +++ b/amaran-th/build.gradle @@ -0,0 +1,22 @@ +plugins { + id 'java' +} + +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/amaran-th/docs/README.md b/amaran-th/docs/README.md new file mode 100644 index 0000000..b45742a --- /dev/null +++ b/amaran-th/docs/README.md @@ -0,0 +1,61 @@ +### 기능 목록 + +- [x] 문구 출력하는 클래스 TextPrinter 생성 및 기능 구현 + - [x] 입력 요구 문구 출력 메서드 + - [x] 결과 출력 메서드 + - [x] 에러 출력 메서드 +- [x] 사용자 입력을 받는 클래스 TextScanner 생성 및 유효성 검사 구현 + - [x] 로또 구입 금액 입력 + - [x] 당첨 번호 입력 + - [x] 보너스 번호 입력 +- [x] 로또 번호를 관리하는 클래스 LottoController 생성 및 기능 구현 + - List(=구매), Lotto(=당첨) 및 구매 수량(=List의 크기), HashMap을 필드 값으로 가진다. + - [x] Lotto를 생성하여(랜덤) 저장하는 메서드 + - [x] Lotto를 생성하여(입력받은 값) 저장하는 메서드 + - [x] 당첨 번호와 구입한 로또들을 비교하여 HashMap을 채우는 메서드 +- [x] 일치 개수에 따른 상금들을 명시한 Enum Prize 생성(등수, 일치 개수, 보너스번호 여부, 및 상금 저장) +- [x] 기타 처리하는 메서드를 구현한 클래스 LottoTool 생성 및 기능 구현 + - [x] 문자열 리스트를 숫자 리스트로 변환하는 메서드 + - [x] 수익률 계산 + - [x] 일련의 번호들(Integer)을 정렬하는 기능 +- [x] 클래스 InputChecker를 만들고 입력값의 유효성을 검사하는 메서드 구현 + - [x] 로또 구입 금액이 1000원으로 나누어 떨어지지 않는 경우 처리 + - [x] 입력한 당첨번호/보너스번호가 1~45 사이의 수인지(오버로딩) + - [x] 입력 문자가 숫자인지 + - [x] List 내에 중복된 값이 존재하는지 +- [x] 에러 처리를 위한 Enum InputType 생성 +- 에러 케이스 + - 구입 금액이 숫자가 아닌 경우 + - 구입 금액이 1000원으로 나누어떨어지지 않는 경우 + - 당첨번호에 숫자가 아닌 데이터가 섞여있는 경우 + - 당첨번호가 6개가 아닌 경우 + - 당첨번호의 숫자범위가 1~45가 아닌 경우 + - 당첨번호에 중복된 수가 포함되어있는 경우 + - 보너스 번호에 숫자가 아닌 경우 + - 보너스 번호의 숫자범위가 1~45가 아닌 경우 + - 당첨번호와 보너스번호 중 중복된 수가 포함되어있는 경우 + +### 요구사항 + +- 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다. +- 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다. +- else 예약어를 쓰지 않는다. + - 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다. else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 + 있는데 switch/case도 허용하지 않는다. +- Java Enum을 적용한다. +- 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다. + - 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리해 구현한다. + - 단위 테스트 작성이 익숙하지 않다면 test/java/lotto/LottoTest를 참고하여 학습한 후 테스트를 구현한다. + +- indent(인덴트, 들여쓰기) depth를 3이 넘지 않도록 구현한다. 2까지만 허용한다. +- [camp.nextstep.edu.missionutils](https://github.com/woowacourse-projects/mission-utils)에서 제공하는 + Randoms 및 Console API를 사용하여 구현해야 한다. + +--- + +- Lotto 클래스 + - 제공된 Lotto 클래스를 활용해 구현해야 한다. + - Lotto에 매개변수가 없는 생성자를 추가할 수 없다. + - numbers의 접근 제어자인 private를 변경할 수 없다. + - Lotto에 필드(인스턴스 변수)를 추가할 수 없다. + - Lotto의 패키지 변경은 가능하다. \ No newline at end of file diff --git a/amaran-th/gradle.properties b/amaran-th/gradle.properties new file mode 100644 index 0000000..16e868e --- /dev/null +++ b/amaran-th/gradle.properties @@ -0,0 +1,2 @@ +org.gradle.jvmargs=-Dfile.encoding=UTF-8 +org.gradle.console=plain diff --git a/amaran-th/gradle/wrapper/gradle-wrapper.jar b/amaran-th/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000..e708b1c Binary files /dev/null and b/amaran-th/gradle/wrapper/gradle-wrapper.jar differ diff --git a/amaran-th/gradle/wrapper/gradle-wrapper.properties b/amaran-th/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 0000000..28ff446 --- /dev/null +++ b/amaran-th/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/amaran-th/gradlew b/amaran-th/gradlew new file mode 100644 index 0000000..4f906e0 --- /dev/null +++ b/amaran-th/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/amaran-th/gradlew.bat b/amaran-th/gradlew.bat new file mode 100644 index 0000000..107acd3 --- /dev/null +++ b/amaran-th/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/amaran-th/settings.gradle b/amaran-th/settings.gradle new file mode 100644 index 0000000..4c0cb4b --- /dev/null +++ b/amaran-th/settings.gradle @@ -0,0 +1 @@ +rootProject.name = 'java-lotto' diff --git a/amaran-th/src/main/java/lotto/Application.java b/amaran-th/src/main/java/lotto/Application.java new file mode 100644 index 0000000..2386e91 --- /dev/null +++ b/amaran-th/src/main/java/lotto/Application.java @@ -0,0 +1,11 @@ +package lotto; + +import lotto.controller.LottoController; + +public class Application { + + public static void main(String[] args) { + LottoController controller = new LottoController(); + controller.play(); + } +} diff --git a/amaran-th/src/main/java/lotto/Lotto.java b/amaran-th/src/main/java/lotto/Lotto.java new file mode 100644 index 0000000..9ad652e --- /dev/null +++ b/amaran-th/src/main/java/lotto/Lotto.java @@ -0,0 +1,41 @@ +package lotto; + +import java.util.HashSet; +import java.util.List; + +public class Lotto { + + private final List numbers; + + public Lotto(List numbers) { + validate(numbers); + this.numbers = numbers; + } + + private void validate(List numbers) { + if (numbers.size() != 6) { + throw new IllegalArgumentException("로또 번호의 개수는 6개여야 한다."); + } + for (int i : numbers) { + if (numbers.indexOf(i) != numbers.lastIndexOf(i)) { + throw new IllegalArgumentException("로또 번호에 중복된 값이 있어선 안된다."); + } + } + for (int i : numbers) { + if (i < 1 || i > 45) { + throw new IllegalArgumentException("로또 번호는 1과 45 사이의 숫자여야 한다."); + } + } + + } + + public List getNumbers() { + return numbers; + } + + + @Override + public String toString() { + return numbers.toString(); + } +} diff --git a/amaran-th/src/main/java/lotto/console/TextPrinter.java b/amaran-th/src/main/java/lotto/console/TextPrinter.java new file mode 100644 index 0000000..a76db1d --- /dev/null +++ b/amaran-th/src/main/java/lotto/console/TextPrinter.java @@ -0,0 +1,58 @@ +package lotto.console; + +import java.text.DecimalFormat; +import java.util.HashMap; +import java.util.List; +import lotto.Lotto; +import lotto.tool.Rank; + +public class TextPrinter { + + private static final DecimalFormat formatter = new DecimalFormat("#,##0"); + + public static void printGetPrice() { + System.out.println("구입금액을 입력해 주세요."); + } + + public static void printLotto(List lottoList) { + System.out.println(lottoList.size() + "개를 구매했습니다."); + for (int i = 0; i < lottoList.size(); i++) { + System.out.println(lottoList.get(i)); + } + } + + public static void printGetWinnerNumber() { + System.out.println("당첨 번호를 입력해 주세요."); + } + + public static void printGetBonnusNumber() { + System.out.println("보너스 번호를 입력해 주세요."); + } + + public static void printTotalResult(HashMap result) { + System.out.println("당첨 통계"); + System.out.println("---"); + for (Rank i : result.keySet()) { + printRankResult(i, result.get(i)); + } + } + + private static void printRankResult(Rank rank, int num) { + + System.out.print(rank.getMatch() + "개 일치"); + if (rank.isIncludeBonnus()) { + System.out.print(", 보너스 볼 일치"); + } + System.out.println(" (" + formatter.format(rank.getPrizeMoney()) + "원) - " + num + "개"); + } + + public static void printReturnRate(double rate) { + System.out.println("총 수익률은 " + String.format("%.1f", rate) + "%입니다."); + } + + public static void printErrorMessage(Exception e) { + System.out.println("[ERROR] " + e.getMessage()); + } + + +} diff --git a/amaran-th/src/main/java/lotto/console/TextScanner.java b/amaran-th/src/main/java/lotto/console/TextScanner.java new file mode 100644 index 0000000..8011fe4 --- /dev/null +++ b/amaran-th/src/main/java/lotto/console/TextScanner.java @@ -0,0 +1,34 @@ +package lotto.console; + +import camp.nextstep.edu.missionutils.Console; +import java.util.List; +import lotto.Lotto; +import lotto.tool.LottoChecker; +import lotto.tool.LottoTool; + +public class TextScanner { + public static int scanPrice() throws IllegalArgumentException{ + String input= Console.readLine(); + LottoChecker.checkIsNumber(input); + int price=Integer.parseInt(input); + LottoChecker.checkDivideThousand(price); + return price; + } + + public static Lotto scanWinnerNumberList() throws IllegalArgumentException{ + String[] inputList=Console.readLine().split(","); + LottoChecker.checkIsNumber(inputList); + List winnerNumberList=LottoTool.convertToIntegerList(inputList); + return new Lotto(winnerNumberList); + } + //TODO : 입력으로 winnerNumberList 받는 거 말고 다른 방법 찾아보기 + public static int scanBonnusNumber(Lotto winnerLotto) throws IllegalArgumentException{ + String input= Console.readLine(); + LottoChecker.checkIsNumber(input); + int bonnusNumber=Integer.parseInt(input); + LottoChecker.checkInRangeNumber(bonnusNumber); + LottoChecker.checkDuplicate(winnerLotto.getNumbers(),bonnusNumber); + return bonnusNumber; + } + +} diff --git a/amaran-th/src/main/java/lotto/controller/LottoController.java b/amaran-th/src/main/java/lotto/controller/LottoController.java new file mode 100644 index 0000000..398fd77 --- /dev/null +++ b/amaran-th/src/main/java/lotto/controller/LottoController.java @@ -0,0 +1,57 @@ +package lotto.controller; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import lotto.Lotto; +import lotto.console.TextPrinter; +import lotto.console.TextScanner; +import lotto.tool.LottoTool; +import lotto.tool.Rank; + +public class LottoController { + + private int price; + private List lottoList; + private Lotto winnerLotto; + private int bonnusNumber; + private double profitRate; + private HashMap result; + + public void play() { + try { + TextPrinter.printGetPrice(); + price = TextScanner.scanPrice(); + + makeLottoList(price); + TextPrinter.printLotto(lottoList); + + TextPrinter.printGetWinnerNumber(); + winnerLotto = TextScanner.scanWinnerNumberList(); + + TextPrinter.printGetBonnusNumber(); + bonnusNumber = TextScanner.scanBonnusNumber(winnerLotto); + + result = LottoTool.calcResult(lottoList, winnerLotto, bonnusNumber); + profitRate = LottoTool.calcReturnRate(result, price); + + TextPrinter.printTotalResult(result); + TextPrinter.printReturnRate(profitRate); + + + } catch (IllegalArgumentException e) { + TextPrinter.printErrorMessage(e); + } + + } + + private void makeLottoList(int price) { + int num = price / 1000; + List lottoList = new ArrayList<>(); + + for (int i = 0; i < num; i++) { + lottoList.add(LottoTool.makeLotto()); + } + this.lottoList = lottoList; + } +} diff --git a/amaran-th/src/main/java/lotto/tool/LottoChecker.java b/amaran-th/src/main/java/lotto/tool/LottoChecker.java new file mode 100644 index 0000000..0f847a6 --- /dev/null +++ b/amaran-th/src/main/java/lotto/tool/LottoChecker.java @@ -0,0 +1,51 @@ +package lotto.tool; + +import java.util.List; + +public class LottoChecker { + + public static final int LENGTH_LIMIT = 6; + public static final int MIN_LOTTO_NUMBER = 1; + public static final int MAX_LOTTO_NUMBER = 45; + + //입력값이 숫자인지 + public static void checkIsNumber(String input) + throws IllegalArgumentException { + if (!input.matches("^[0-9]+$")) { + throw new IllegalArgumentException("보너스 번호는 숫자여야 한다."); + } + } + + public static void checkIsNumber(String[] numberList) + throws IllegalArgumentException { + for (int i = 0; i < numberList.length; i++) { + if (!numberList[i].matches("^[0-9]+$")) { + throw new IllegalArgumentException("로또 번호는 숫자여야 한다."); + } + } + } + + //입력값이 1~45 사이의 수인지 + public static void checkInRangeNumber(int number) + throws IllegalArgumentException { + if (number < MIN_LOTTO_NUMBER || number > MAX_LOTTO_NUMBER) { + throw new IllegalArgumentException("보너스 번호는 1과 45 사이의 숫자여야 한다."); + } + } + + //수가 1000으로 나누어떨어지는지 + public static void checkDivideThousand(int number) + throws IllegalArgumentException { + if (number % 1000 != 0) { + throw new IllegalArgumentException("구입 금액은 1000으로 나누어 떨어져야 한다."); + } + } + + //리스트의 중복된 값이 있는지 + public static void checkDuplicate(List numberList, int bonnusNumber) + throws IllegalArgumentException { + if (numberList.contains(bonnusNumber)) { + throw new IllegalArgumentException("보너스 번호는 로또 번호와 중복되어선 안된다."); + } + } +} diff --git a/amaran-th/src/main/java/lotto/tool/LottoTool.java b/amaran-th/src/main/java/lotto/tool/LottoTool.java new file mode 100644 index 0000000..9715b90 --- /dev/null +++ b/amaran-th/src/main/java/lotto/tool/LottoTool.java @@ -0,0 +1,92 @@ +package lotto.tool; + +import camp.nextstep.edu.missionutils.Randoms; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.stream.Collectors; +import lotto.Lotto; + +public class LottoTool { + + public static Lotto makeLotto() { + List numberList = Randoms.pickUniqueNumbersInRange(LottoChecker.MIN_LOTTO_NUMBER, + LottoChecker.MAX_LOTTO_NUMBER, LottoChecker.LENGTH_LIMIT); + List sortedList = numberList.stream().sorted().collect(Collectors.toList()); + return new Lotto(sortedList); + } + + //변환 + public static List convertToIntegerList(String[] inputList) { + List numberList = new ArrayList<>(); + for (int i = 0; i < LottoChecker.LENGTH_LIMIT; i++) { + numberList.add(Integer.parseInt(inputList[i])); + } + return numberList; + } + + public static HashMap calcResult(List lottoList, Lotto winnerLotto, + int bonnusNumber) { + HashMap hashMap = initResult(); + for (Lotto l : lottoList) { + Rank rank = rankLotto(l, winnerLotto, bonnusNumber); + if (!rank.equals(Rank.LOSE)) { + hashMap.merge(rank, 1, Integer::sum); + } + } + return hashMap; + } + + //초기 result + private static HashMap initResult() { + HashMap result = new HashMap<>(); + for (Rank r : Rank.values()) { + if (r.equals(Rank.LOSE)) { + continue; + } + result.put(r, 0); + } + return result; + } + + //로또번호의 순위(Prize)를 매기는 메서드 + private static Rank rankLotto(Lotto lotto, Lotto winnerLotto, int bonnusNumber) { + int match = 0; + boolean isIncludeBonnus = false; + for (int i : winnerLotto.getNumbers()) { + if (lotto.getNumbers().contains(i)) { + match++; + } + } + if (lotto.getNumbers().contains(bonnusNumber)) { + match++; + isIncludeBonnus = true; + } + return getPrize(match, isIncludeBonnus); + } + + private static Rank getPrize(int match, boolean isIncludeBonnus) { + boolean isSecondOrThird = match == 5; + if (isSecondOrThird) { + if (isIncludeBonnus) { + return Rank.SECOND; + } + return Rank.THIRD; + } + for (Rank r : Rank.values()) { + if (match == r.getMatch()) { + return r; + } + } + return Rank.LOSE; + } + + public static double calcReturnRate(HashMap result, int price) { + int profit = 0; + for (Rank i : result.keySet()) { + profit += i.getPrizeMoney() * result.get(i); + } + double rate = 100 * profit / (double) price; + return rate; + } +} diff --git a/amaran-th/src/main/java/lotto/tool/Rank.java b/amaran-th/src/main/java/lotto/tool/Rank.java new file mode 100644 index 0000000..2cbfbc0 --- /dev/null +++ b/amaran-th/src/main/java/lotto/tool/Rank.java @@ -0,0 +1,31 @@ +package lotto.tool; + +public enum Rank { + FIRST(6,false,2_000_000_000), + SECOND(5,true,30_000_000), + THIRD(5,false,1_500_000), + FORTH(4,false,50_000), + FIFTH(3,false,5_000), + LOSE(-1,false,0); + + private final int match; + private final boolean isIncludeBonnus; + private final int prizeMoney; + Rank(int match, boolean isIncludeBonnus, int prizeMoney){ + this.match=match; + this.isIncludeBonnus=isIncludeBonnus; + this.prizeMoney=prizeMoney; + } + + public int getMatch() { + return match; + } + + public boolean isIncludeBonnus() { + return isIncludeBonnus; + } + + public int getPrizeMoney() { + return prizeMoney; + } +} diff --git a/amaran-th/src/test/java/lotto/ApplicationTest.java b/amaran-th/src/test/java/lotto/ApplicationTest.java new file mode 100644 index 0000000..a15c7d1 --- /dev/null +++ b/amaran-th/src/test/java/lotto/ApplicationTest.java @@ -0,0 +1,61 @@ +package lotto; + +import camp.nextstep.edu.missionutils.test.NsTest; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static camp.nextstep.edu.missionutils.test.Assertions.assertRandomUniqueNumbersInRangeTest; +import static camp.nextstep.edu.missionutils.test.Assertions.assertSimpleTest; +import static org.assertj.core.api.Assertions.assertThat; + +class ApplicationTest extends NsTest { + private static final String ERROR_MESSAGE = "[ERROR]"; + + @Test + void 기능_테스트() { + assertRandomUniqueNumbersInRangeTest( + () -> { + run("8000", "1,2,3,4,5,6", "7"); + assertThat(output()).contains( + "8개를 구매했습니다.", + "[8, 21, 23, 41, 42, 43]", + "[3, 5, 11, 16, 32, 38]", + "[7, 11, 16, 35, 36, 44]", + "[1, 8, 11, 31, 41, 42]", + "[13, 14, 16, 38, 42, 45]", + "[7, 11, 30, 40, 42, 43]", + "[2, 13, 22, 32, 38, 45]", + "[1, 3, 5, 14, 22, 45]", + "3개 일치 (5,000원) - 1개", + "4개 일치 (50,000원) - 0개", + "5개 일치 (1,500,000원) - 0개", + "5개 일치, 보너스 볼 일치 (30,000,000원) - 0개", + "6개 일치 (2,000,000,000원) - 0개", + "총 수익률은 62.5%입니다." + ); + }, + List.of(8, 21, 23, 41, 42, 43), + List.of(3, 5, 11, 16, 32, 38), + List.of(7, 11, 16, 35, 36, 44), + List.of(1, 8, 11, 31, 41, 42), + List.of(13, 14, 16, 38, 42, 45), + List.of(7, 11, 30, 40, 42, 43), + List.of(2, 13, 22, 32, 38, 45), + List.of(1, 3, 5, 14, 22, 45) + ); + } + + @Test + void 예외_테스트() { + assertSimpleTest(() -> { + runException("1000j"); + assertThat(output()).contains(ERROR_MESSAGE); + }); + } + + @Override + public void runMain() { + Application.main(new String[]{}); + } +} diff --git a/amaran-th/src/test/java/lotto/LottoTest.java b/amaran-th/src/test/java/lotto/LottoTest.java new file mode 100644 index 0000000..0f3af0f --- /dev/null +++ b/amaran-th/src/test/java/lotto/LottoTest.java @@ -0,0 +1,27 @@ +package lotto; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import java.util.List; + +import static org.assertj.core.api.Assertions.assertThatThrownBy; + +class LottoTest { + @DisplayName("로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.") + @Test + void createLottoByOverSize() { + assertThatThrownBy(() -> new Lotto(List.of(1, 2, 3, 4, 5, 6, 7))) + .isInstanceOf(IllegalArgumentException.class); + } + + @DisplayName("로또 번호에 중복된 숫자가 있으면 예외가 발생한다.") + @Test + void createLottoByDuplicatedNumber() { + // TODO: 이 테스트가 통과할 수 있게 구현 코드 작성 + assertThatThrownBy(() -> new Lotto(List.of(1, 2, 3, 4, 5, 5))) + .isInstanceOf(IllegalArgumentException.class); + } + + // 아래에 추가 테스트 작성 가능 +}