Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[로또] 원준영 미션 제출합니다. #187

Open
wants to merge 37 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
37 commits
Select commit Hold shift + click to select a range
ab5108e
CHORE: gitignore에 커밋 컨벤션 파일 추가
Junyoung-WON Nov 3, 2023
cf41668
CHORE: 커밋 컨벤션 파일 생성
Junyoung-WON Nov 3, 2023
586ad5e
DOCS: 구현 기능 상세 명세
Junyoung-WON Nov 5, 2023
1929a68
ADD: User 클래스 파일 생성
Junyoung-WON Nov 5, 2023
52e98f9
ADD: 사용자 입력을 검증하는 Validator 클래스 파일 생성
Junyoung-WON Nov 6, 2023
831537c
FEAT: 입력된 문자열이 정수인지 검증하는 함수
Junyoung-WON Nov 6, 2023
1c8f916
FEAT: 입력 문자열의 값이 정수인지 확인하는 함수 구현
Junyoung-WON Nov 6, 2023
6d246d2
FEAT: 입력받은 로또 구매 금액이 1,000원으로 나누어 떨어지는지 검사
Junyoung-WON Nov 6, 2023
45aae2d
FEAT: 입력받은 문자열이 숫자이면서 0보다 큰지 확인하고 Exception을
Junyoung-WON Nov 6, 2023
20a2581
FEAT: 구입 금액이 1,000원으로 나누어 떨어지지 않으면 Exception을
Junyoung-WON Nov 6, 2023
a19ba99
STYLE: 코드 포맷팅 수행
Junyoung-WON Nov 6, 2023
e304b6b
FIX: isDividedUpThousand 함수의 예외 발생 문구 수정
Junyoung-WON Nov 6, 2023
71c50b1
FEAT: 사용자로부터 구입 금액을 입력받는 기능 구현
Junyoung-WON Nov 6, 2023
88db08f
FEAT: 입력받은 로또 번호 문자열이 올바른지 확인하는 기능 구현
Junyoung-WON Nov 6, 2023
30736a4
DOCS: 로또 당첨 번호 입력 시 번호의 개수를 검사하는 기능을 추가 명세
Junyoung-WON Nov 7, 2023
95a5c68
FEAT: 로또 당첨번호의 개수가 6개인지 확인하는 메소드 구현
Junyoung-WON Nov 7, 2023
a6806b4
FEAT: 쉼표의 여부를 확인하는 메소드의 반환형을 변경
Junyoung-WON Nov 7, 2023
d6b7fae
FEAT: validateLottoNumber에 countsLottoNumbers 구현
Junyoung-WON Nov 7, 2023
0815f01
FEAT: 로또 번호가 1과 45사이의 정수인지 확인하는 메소드 구현
Junyoung-WON Nov 7, 2023
99ba8ff
REFACTOR: 로또 범위 검사 메소드의 이름 변경
Junyoung-WON Nov 7, 2023
d96b591
FEAT: 로또 번호의 중복을 검사하는 메소드 구현
Junyoung-WON Nov 7, 2023
68e5203
DOCS: Lotto 클래스에 대한 내용을 추가 명세
Junyoung-WON Nov 8, 2023
bf6c32b
FEAT: 로또 클래스 내부에 로또 번호가 올바른지 검증하는 메소드 구현
Junyoung-WON Nov 8, 2023
a324f81
FEAT: 정수 리스트로 변환 가능한 문자열인지 확인하는 기능으로 변경
Junyoung-WON Nov 8, 2023
0b9d369
FEAT: 사용자로부터 당첨 번호를 입력받는 기능 구현
Junyoung-WON Nov 8, 2023
3dbfeb2
FEAT: 사용자가 구매한 금액만큼 로또를 발행하는 기능 구현
Junyoung-WON Nov 8, 2023
7efb1c4
FEAT: 로또 구매 시 구매한 개수와 로또 번호를 함께 출력시키도록 수정
Junyoung-WON Nov 8, 2023
297d0b5
FIX: 구매할 로또 개수가 올바르게 연산되지 않는 오류 수정 및 로또번호
Junyoung-WON Nov 8, 2023
0cb19de
FEAT: 입력받은 당첨 번호를 정수형 리스트로 반환하는 기능 추가
Junyoung-WON Nov 8, 2023
2f12ebc
FEAT: 에러 발생 시 에러 메시지만 출력되도록 수정
Junyoung-WON Nov 8, 2023
20848e7
FEAT: 메시지 출력 시 첫 부분에 개행을 하도록 수정
Junyoung-WON Nov 8, 2023
d00a5ab
FEAT: 보너스 번호에 대한 입력 값의 검증을 구현
Junyoung-WON Nov 8, 2023
8428aab
FEAT: 사용자에게 보너스 번호를 입력받아 검증을 거치며 잘못된 값일 경우
Junyoung-WON Nov 8, 2023
6b84dd2
ADD: 당첨자 순위에 대한 Enum 클래스 파일 생성
Junyoung-WON Nov 8, 2023
c823130
FEAT: 등수에 따른 상금과 표시할 문자열, 당첨 개수를 구현
Junyoung-WON Nov 8, 2023
95d0969
ADD: 당첨된 개수와 수입금의 수익률을 계산하여 출력하는 통계 클래스
Junyoung-WON Nov 8, 2023
c379f32
FEAT: 로또 게임 기능 구현
Junyoung-WON Nov 8, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ build/
!gradle/wrapper/gradle-wrapper.jar
!**/src/main/**
!**/src/test/**
./.gitmessage.txt

### macOS ###
.DS_Store
Expand Down
24 changes: 24 additions & 0 deletions .gitmessage.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
###### <제목> ######
# <타입>: <제목> 형식으로 아래 공백 라인에 작성
# 제목은 최대 50글자, 제목 끝에 마침표 금지, '무엇'을 했는지 명확하게 작성

# 아래 공백 지우지 않기(제목, 본문의 분리)

###### <본문> ######
# 본문(추가 설명)을 아랫줄에 작성

###### <꼬릿말> #####
# 꼬릿말(footer)을 아랫줄에 작성 (현재 커밋과 관련된 이슈 번호 추가 등)

###### <타입> ######
# FEAT : 기능 추가 및 수정
# FIX : 버그 수정
# DOCS : 문서 수정
# ADD : 파일, 디렉터리 생성
# REMOVE : 파일, 디렉터 삭제
# TEST : 테스트 코드 추가
# REFACTOR : 코드 리팩토링
# STYLE : 코드 의미에 영향을 주지 않는 변경사항(코드 포맷팅 등)
# CHORE : 빌드 부분 혹은 패키지 매니저 수정사항(gitignore 등)
# CICD : CI/CD 관련 설정
###################
80 changes: 80 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
3주차 미션 - 로또
==================
------------------

## 구현 기능 목록
### 로또 게임의 규칙
로또 게임 규칙은 아래와 같다.
> - 로또 번호의 숫자 범위는 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]"로 시작하는 에러 메시지를 출력한 후 그 부분부터 입력을 다시 받는다.
> + `IllegalArgumentException`, `IllegalStateException` 등과 같은 명확한 유형을 처리한다.

위 규칙을 바탕으로 필요한 기능을 구현한다.

### 사용자 입력
사용자의 입력은 `Console.readLine()`을 활용한다.
* 사용자로부터 로또 구입 금액을 입력 받는다.
+ 로또는 장당 1,000원이며, 구입 금액은 1,000원 단위로 입력 받는다.
+ 1,000원으로 나누어 떨어지지 않는 경우 예외 처리 후 다시 입력 받는다.
* 사용자로부터 당첨 번호를 입력 받는다.
+ 번호는 쉼표(,)를 기준으로 구분한다.
+ 로또 번호의 개수는 6개이다.
+ 로또 번호는 1과 45사이의 정수이며, 중복되지 않는다.
+ 잘못된 입력을 받으면 예외 처리 후 다시 입력 받는다.
* 사용자로부터 보너스 번호를 입력받는다.
+ 1과 45사이의 정수이며, 마찬가지로 중복되지 않는다.
+ 잘못된 입력을 받으면 예외 처리 후 다시 입력 받는다.
### 로또 번호 생성
로또를 발행하여 출력한다.
* 사용자가 구매한 수량만큼 로또를 발행한다(로또 번호 생성).
+ 로또 번호를 무작위로 생성하는 기능은 `Randoms.pickUniqueNumbersInRange()`을 활용한다.
* 발행한 수량과 로또 번호를 출력한다.
+ 로또 번호 출력 시 오름차순으로 정렬하여 출력한다.
### 당첨 내역 출력
* 사용자가 입력한 당첨 번호와 발행한 로또 번호를 비교한다.
+ 일치한 개수와 상금, 일치한 로또의 개수를 출력한다.
* 번호를 비교하여 일치한 개수에 따라 등수가 매겨진다.
* 등수에 따른 상금을 총합한다.
### 수익률 계산
* 수익률을 계산하여 출력한다.
+ 구매한 금액에 대한 당첨금의 비율을 계산하여 출력한다.
+ 출력 시 소수점 둘째 자리에서 반올림한다.
### 예외 처리
사용자 입력에 대한 예외를 체크하고 처리한다.
* 로또 구입 금액의 예외를 처리한다.
+ 양의 정수만을 입력받는다.
- 숫자 이외의 다른 문자가 입력되면 예외 처리한다.
+ 1,000원으로 나누어 떨어지지 않으면 예외 처리한다.
* 당첨 번호 입력에 대한 예외를 처리한다.
+ 쉼표와 숫자 이외에 다른 문자가 있는지 확인한다.
+ 쉼표로 구분된 숫자들이 중복이 있는지 검사한다.
+ 쉼표로 구분된 숫자가 1에서 45사이의 숫자인지 확인한다.
* 보너스 번호 입력에 대한 예외를 처리한다.
+ 정수 이외의 다른 문자는 아닌지 확인한다.
+ 숫자가 1과 45 사이의 정수인지 확인한다.
+ 당첨 번호와 중복인지 아닌지 확인한다.
* 예외 상황 시 "[ERROR]"로 시작하는 에러 문구를 출력해야 한다.
+ 각 예외 상황에 맞는 에러 문구를 출력한다.
+ 예외 처리 후 입력을 다시 받는다.

추가적으로 아래 사항들을 따른다.
### Lotto 클래스 활용
* 제공된 Lotto 클래스를 활용하여 구현한다.
* 테스트코드인 LottoTest를 참고하여 구현한다.
+ 로또 번호에 대한 검증이 Lotto 클래스 안에서 일어나야 한다.
24 changes: 23 additions & 1 deletion src/main/kotlin/lotto/Application.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,27 @@
package lotto

import camp.nextstep.edu.missionutils.Console

fun main() {
TODO("프로그램 구현")
val user = User()
user.inputPurchaseMoney()
user.purchaseLottoTickets()

var lotto: Lotto
var validation: Boolean = false
var winning = listOf<Int>()
while (!validation) {
try {
winning = user.inputLottoNumbers()
lotto = Lotto(winning)
validation = true
} catch (e: IllegalArgumentException){
println("[ERROR] ${e.message}")
}
}
user.inputBonusNumber()
val winningNumbers = lotto.getWinningNumbers()

Statistics.howManyWins(lotto.getWinningNumbers(), user)

}
31 changes: 29 additions & 2 deletions src/main/kotlin/lotto/Lotto.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,35 @@ package lotto

class Lotto(private val numbers: List<Int>) {
init {
require(numbers.size == 6)
// 로또 번호의 개수는 6개여야 한다.
require(numbers.size == 6) { "로또 번호는 6개여야 합니다." }
// 로또 번호는 1부터 45 사이의 정수여야 한다.
require(inCorrectRange(numbers)) { "로또 번호는 1부터 45 사이의 숫자여야 합니다." }
// 로또 번호는 중복이 없어야 한다.
require(hasDuplicateNumbers(numbers)) { "로또 번호는 중복되지 않아야 합니다." }
}

// TODO: 추가 기능 구현
private fun inCorrectRange(lottoNumbers: List<Int>): Boolean {
for (number in lottoNumbers) {
if (number !in 1..45) {
return false
}
}
return true
}

private fun hasDuplicateNumbers(lottoNumbers: List<Int>): Boolean {
val validator = mutableListOf<Int>()
for (lotto in lottoNumbers) {
if (validator.contains(lotto.toInt())) {
return false
}
validator.add(lotto.toInt())
}
return true
}

fun getWinningNumbers(): List<Int>{
return numbers
}
}
49 changes: 49 additions & 0 deletions src/main/kotlin/lotto/Statistics.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
package lotto

class Statistics {
companion object {
fun howManyWins(winNumbers: List<Int>, user: User) {
for (lotto in user.lottoTickets) {
compareWinNumbers(winNumbers, lotto, user.bonusNumber)
}
val returnRate = rateOfReturn(user.purchaseMoney)
printResult(returnRate)
}

private fun compareWinNumbers(winNumbers: List<Int>, lotto: List<Int>, bonus: Int) {
var counts: Int = 0
for (winNumber in winNumbers) {
if (lotto.contains(winNumber)) {
counts++
}
}
if (counts == 3) WinEnum.FIFTH.counts++
else if (counts == 4) WinEnum.FOURTH.counts++
else if (counts == 5){
if (lotto.contains(bonus)) WinEnum.SECOND.counts++
else WinEnum.THIRD.counts++
}
else if (counts == 6) WinEnum.FIRST.counts
}

private fun rateOfReturn(purchaseMoney: Int): Double {
val benefit = WinEnum.FIFTH.win * WinEnum.FIFTH.counts
+WinEnum.FOURTH.win * WinEnum.FOURTH.counts
+WinEnum.THIRD.win * WinEnum.THIRD.counts
+WinEnum.SECOND.win * WinEnum.SECOND.counts
+WinEnum.FIRST.win * WinEnum.FIRST.counts
return (benefit / purchaseMoney) * 100.0
}

private fun printResult(returnRate: Double) {
val roundOff = String.format("%.2f", returnRate)
println("\n당첨 통계\n---")
println(WinEnum.FIFTH.printer)
println(WinEnum.FOURTH.printer)
println(WinEnum.THIRD.printer)
println(WinEnum.SECOND.printer)
println(WinEnum.FIRST.printer)
println("총 수익률은 ${roundOff}%입니다.")
}
}
}
64 changes: 64 additions & 0 deletions src/main/kotlin/lotto/User.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package lotto

import camp.nextstep.edu.missionutils.Console
import camp.nextstep.edu.missionutils.Randoms

class User {
var purchaseMoney: Int = 0
val lottoTickets = mutableListOf<List<Int>>()
var bonusNumber: Int = 0
val validator = Validator()

fun inputPurchaseMoney() {
var validation: Boolean = false
var money: String = ""
while (!validation) {
println("구입금액을 입력해 주세요.")
money = Console.readLine()
validation = validator.validatePurchaseMoney(money)
}
purchaseMoney = money.toInt()
}

fun purchaseLottoTickets() {
val ticketCounts: Int = purchaseMoney / 1000
println("\n${ticketCounts}개를 구매했습니다.")
for (count in 0 until ticketCounts) {
val numbers = Randoms.pickUniqueNumbersInRange(1, 45, 6)
numbers.sort()
lottoTickets.add(numbers)
println(numbers)
}
}

fun inputLottoNumbers(): List<Int> {
var validation: Boolean = false
var lottoNumbers: String = ""
while (!validation) {
println("\n당첨 번호를 입력해 주세요.")
lottoNumbers = Console.readLine()
validation = validator.couldConvertIntList(lottoNumbers)
}
return convertIntList(lottoNumbers)
}

private fun convertIntList(winning: String): List<Int> {
var winningNumbers = mutableListOf<Int>()
for (number in winning.split(",")) {
winningNumbers.add(number.toInt())
}
winningNumbers.sort()
return winningNumbers
}

fun inputBonusNumber() {
var validation: Boolean = false
var bonus: String = ""
while (!validation) {
println("\n보너스 번호를 입력해 주세요.")
bonus = Console.readLine()
validation = validator.validateBonusNumber(bonus)
}
bonusNumber = bonus.toInt()
}
}
66 changes: 66 additions & 0 deletions src/main/kotlin/lotto/Validator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
package lotto

class Validator {
fun validatePurchaseMoney(purchaseMoney: String): Boolean {
return try {
isNumberOverZero(purchaseMoney) && isDividedUpThousand(purchaseMoney.toInt())
} catch (e: IllegalArgumentException) {
println("[ERROR] ${e.message}")
false
}
}

private fun isNumberOverZero(input: String): Boolean {
val number = input.toIntOrNull()
return if (number != null && number > 0) {
true
} else {
throw IllegalArgumentException("0보다 큰 정수를 입력해주세요.")
}
}

private fun isDividedUpThousand(money: Int): Boolean {
return if (money % 1000 == 0) {
true
} else {
throw IllegalArgumentException("구입 금액이 1000원으로 나누어 떨어지지 않습니다.")
}
}

fun couldConvertIntList(winning: String): Boolean {
return try {
containsComma(winning)
val winningNumbers: List<String> = winning.split(",")
for (number in winningNumbers) {
isNumberOverZero(number)
}
true
} catch (e: IllegalArgumentException) {
println("[ERROR] ${e.message}")
false
}
}

private fun containsComma(lotto: String) {
if (!lotto.contains(',')) {
throw IllegalArgumentException("로또 번호는 쉼표(,)로 구분지어주세요.")
}
}

fun validateBonusNumber(bonus: String): Boolean {
return try {
isNumberOverZero(bonus)
inCorrectRange(bonus.toInt())
true
} catch (e: IllegalArgumentException) {
println("[ERROR] ${e.message}")
false
}
}

private fun inCorrectRange(number: Int) {
if (number !in 1..45){
throw IllegalArgumentException("로또 번호는 1부터 45 사이의 숫자여야 합니다.")
}
}
}
9 changes: 9 additions & 0 deletions src/main/kotlin/lotto/WinEnum.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package lotto

enum class WinEnum(val win: Int, val printer: String, var counts: Int) {
FIFTH(5000, "3개 일치 (5,000원) - ${FIFTH.counts}", 0),
FOURTH(50000, "4개 일치 (50,000원) - ${FOURTH.counts}", 0),
THIRD(1500000, "5개 일치 (1,500,000원) - ${THIRD.counts}", 0),
SECOND(30000000, "5개 일치, 보너스 볼 일치 (30,000,000원) - ${SECOND.counts}", 0),
FIRST(2000000000, "6개 일치 (2,000,000,000원) - ${FIRST.counts}", 0)
}