-
Notifications
You must be signed in to change notification settings - Fork 107
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
[로또] 이홍진 미션 제출합니다. #84
base: main
Are you sure you want to change the base?
Changes from all commits
c6a5be2
6c413a6
05b9918
56a393d
0399497
5097ba8
2575435
819399e
0aa480e
604ae98
e74d8d6
4f8f106
dbc4e21
a47d1ea
a686edc
20c3657
c767fd4
3e46f0d
f6fa329
90ddcfd
642c483
17aa913
e0ad875
1bcf0d9
896a041
3054191
43ac563
ec54bce
dc3ffc7
fd64384
a9dc229
2db253f
87c93f3
cd3c575
278f53b
09aa942
6defc69
acbefab
1af1e9b
6242b23
6a2f59a
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,215 @@ | ||
# kotlin-lotto-precourse | ||
# 3주차 로또 | ||
## 프로젝트 개요 | ||
사용자가 돈을 낸만큼 로또를 발행하고, 당첨 번호를 입력하여 발행된 로또에서 몇개가 당첨됐는지 또 수익률이 몇 퍼센트인지 당첨 통계를 내주는 프로그램 | ||
|
||
## 프로젝트 흐름 | ||
1. 사용자가 금액을 제시하면 해당 금액에 맞게 로또를 구매한다. | ||
2. 사용자는 1~45까지의 중복되지 않는 6개 숫자로 이루어진 로또 당첨 번호를 쉼표(,) 기준으로 제출한다. | ||
3. 이후 제출한 6개의 숫자와 중복되지 않는 보너스 번호를 제출한다. | ||
4. 구매한 로또의 개수의 따라 로또를 발행한다. | ||
5. 발행한 로또의 개수와 로또 번호를 보여준다. | ||
6. 제출 했던 당첨 번호(보너스 번호 포함)와 발행된 로또들과의 당첨 여부를 확인한다. | ||
7. 번호가 몇 개 일치 했는가에 따라 당첨 내역을 출력한다. | ||
8. 당첨 금액과 로또 구매 금액의 비율로 수익률을 구하고 출력한다. | ||
|
||
## 프로젝트 구조 | ||
``` | ||
lotto | ||
├── controller | ||
│ └── LottoController.kt | ||
├── model | ||
│ ├── Lotto.kt | ||
│ ├── LottoFactory.kt | ||
│ ├── LottoRank.kt | ||
│ └── LottoResultCalculator.kt | ||
├── view | ||
│ ├── InputView.kt | ||
│ └── OutputView.kt | ||
└── util | ||
├── constant | ||
│ ├── ErrorMessages.kt | ||
│ ├── InputConstants.kt | ||
│ ├── OutputConstants.kt | ||
│ └── LottoRules.kt | ||
└── validator | ||
├── BonusNumberValidator.kt | ||
├── LottoNumberValidator.kt | ||
└── PurchaseAmountValidator.kt | ||
``` | ||
|
||
## 기능 요구 사항 | ||
### 입력 | ||
- 로또 구입 금액 | ||
- 1,000 단위로 입력 | ||
- 1,000으로 나눠 떨어지지 않으면 **예외 처리** | ||
- 로또 번호 | ||
- 번호는 쉼표로 구분 | ||
- 1에서 45까지 중복이 없는 6개의 자연수 | ||
- 보너스 번호 | ||
- 1에서 45까지 당첨 번호와 중복이 없는 자연수 하나 | ||
|
||
### 출력 | ||
- 발행 로또 정보 | ||
- 로또의 수량 | ||
- 오름차순으로 정렬된 로또 번호 | ||
- 당첨 내역 출력 | ||
- 일치 번호가 3개부터 6개까지 몇 개인지 상금과 함께 출력 | ||
- 5개 일치의 경우 | ||
- 2등: 보너스 번호가 일치 | ||
- 3등: 보너스 번호가 불일치 | ||
- 수익률 | ||
- 소수점 둘째 자리에서 반올림한 총 수익률 | ||
|
||
### 예외 상황 | ||
- 예외 상황 시 에러 문구를 출력하고 그 부분부터 입력을 다시 받음 | ||
- `Exception`이 아닌 `IllegalArgumentException`, `IllegalStateException` 등과 같은 명확한 유형으로 처리 | ||
|
||
### 실행 결과 예시 | ||
``` | ||
구입금액을 입력해 주세요. | ||
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%입니다. | ||
``` | ||
|
||
## 프로그래밍 요구 사항 | ||
- 제한 사항 | ||
- `indent depth`는 2까지 허용 | ||
- `method`는 한가지 일만 수행 | ||
- 15라인을 넘기지 않도록 구현 | ||
- 프로그램 실행 시작점은 `Application`의 `main()` | ||
- 프로그램 종료 시 `System.exit()`나 `exitProcess()`를 호출하지 않음 | ||
- 적용 사항 | ||
- `enum class` 적용 | ||
- 주어진 `Lotto class` 사용 | ||
- `number`이외 필드는 추가 불가 | ||
- `number`의 접근 제어자는 `private`로 고정 | ||
- 패키지는 변경 가능 | ||
- 지향 사항 | ||
- `else`사용은 지양 | ||
- `if` 조건절에서 값을 `return`하는 방식으로 구현 | ||
- 테스트는 기능 단위로 작성 | ||
- UI로직은 제외(System.out, System.in, Scanner) | ||
|
||
## 기능 목록 | ||
### 입력 | ||
- 구입 금액 | ||
- [X] 숫자로 입력 | ||
- 로또 번호 | ||
- [X] 쉼표(,)로 연결된 1~45 사이의 6개의 서로 다른 자연수 입력 | ||
- 보너스 번호 | ||
- [X] 1~45 사이의 당첨 번호와 중복이 없는 자연수 하나 | ||
|
||
### 로또 발행 | ||
- 로또의 수량 | ||
- [X] (입력된 금액 // 1000)개 | ||
- 로또 발행 | ||
- [X] 1~45 사이의 6개의 서로 다른 자연수를 난수로 생성 | ||
- `Randoms.pickUniqueNumbersInRange(1, 45, 6)` | ||
- [X] 로또의 수량 만큼 반복 | ||
- [X] 로또 숫자는 오름차순으로 정렬 | ||
|
||
### 당첨 통계 | ||
- 당첨 번호 확인 | ||
- [X] 발행된 로또의 번호와 입력한 로또 번호를 비교 | ||
- 일치 번호의 개수에 따라 당첨 등수의 개수 확인 | ||
- 5개가 일치할 경우 | ||
- 남은 한개의 숫자와 입력한 보너스 번호 비교 | ||
- [X] 두 수가 다를 경우: 3등 | ||
- [X] 두 수가 같을 경우: 2등 | ||
- 수익률 | ||
- [X] (총 당첨액 / 총 구매액) * 100을 소수점 둘째 자리에서 반올림 한 % | ||
- [X] 총 당첨액 | ||
- (당첨 금액 * 당첨 갯수)를 모두 더한 값 | ||
- 3개 일치: 5,000 | ||
- 4개 일치: 50,000 | ||
- 5개 일치 | ||
- 3등: 1,500,000 | ||
- 2등: 30,000,000 | ||
- 6개 일치: 2,000,000,000 | ||
- 총 구매액: 입력 구입 금액 | ||
|
||
### 출력 | ||
- 당첨 통계 | ||
- [X] 3개부터 6개 일치하는 로또가 몇 개인지 표기 | ||
- 총 수익률 | ||
- [X] 총 수익률 표기 | ||
- [X] (수익 / 투자금) * 100 | ||
- ex) (5,000 / 8,000) * 100 = 62.5 | ||
|
||
## 오류 처리 | ||
- 구입 금액 | ||
- 숫자로 입력 | ||
- 숫자가 아닐 경우: "[ERROR] 구입 금액은 숫자여야 합니다." | ||
- 1,000으로 나눠 떨어지지 않을 경우: "[ERROR] 구입 금액은 1,000원 단위여야 합니다." | ||
- 10억원을 넘길 경우: "[ERROR] 구입 금액은 10억원 이하여야 합니다." | ||
- 로또 번호 | ||
- 쉼표(,)로 연결된 1~45 사이의 6개의 서로 다른 자연수 입력 | ||
- 쉼표로 연결 되지 않을 경우: "[ERROR] 로또 번호는 쉼표로 구분해야 합니다." | ||
- 1~45 사이의 수가 아닐 경우: "[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다." | ||
- 6개가 아닐 경우: "[ERROR] 로또 번호는 6개여야 합니다." | ||
- 서로 다르지 않을 경우: "[ERROR] 로또 번호는 중복될 수 없습니다." | ||
- 자연수가 아닐 경우: "[ERROR] 로또 번호는 자연수여야 합니다." | ||
- 보너스 번호 | ||
- 1~45 사이의 당첨 번호와 중복이 없는 자연수 하나 | ||
- 1~45 사이의 수가 아닐 경우: "[ERROR] 보너스 번호는 1부터 45 사이의 숫자여야 합니다." | ||
- 당첨 번호와 중복이 있을 경우: "[ERROR] 보너스 번호는 로또 번호와 중복될 수 없습니다." | ||
- 자연수가 아닐 경우: "[ERROR] 보너스 번호는 자연수여야 합니다." | ||
|
||
## 테스트 | ||
- Lotto 테스트 | ||
- 초기화 검증 테스트 | ||
- [X] 로또 번호 개수 테스트 | ||
- [X] 로또 번호 범위 테스트 | ||
- [X] 로또 번호 중복 테스트 | ||
- [X] 로또 번호 불변 테스트 | ||
- 로또 등수 테스트 | ||
- [X] 당첨 | ||
- [X] 꽝 | ||
|
||
- LottoFactory 테스트 | ||
- [X] 변수 불변 테스트 | ||
- [X] 로또 생성 테스트 | ||
|
||
- LottoResultCalculator 테스트 | ||
- [X] 로또 결과 테스트 | ||
- [X] 총 수익률 테스트 | ||
|
||
- 예외 상황 테스트 | ||
- 구입 금액 | ||
- [X] 숫자가 아닐 경우 | ||
- [X] 1,000으로 나눠 떨어지지 않을 경우 | ||
- [X] 1,000에서 10억을 넘길 경우 | ||
- 로또 번호 | ||
- [X] 쉼표로 연결 되지 않을 경우 | ||
- [X] 1~45 사이의 수가 아닐 경우 | ||
- [X] 6개가 아닐 경우 | ||
- [X] 서로 다르지 않을 경우 | ||
- [X] 자연수가 아닐 경우 | ||
- 보너스 번호 | ||
- [X] 1~45 사이의 수가 아닐 경우 | ||
- [X] 당첨 번호와 중복이 있을 경우 | ||
- [X] 자연수가 아닐 경우 |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,7 @@ | ||
package lotto | ||
|
||
import lotto.controller.LottoController | ||
|
||
fun main() { | ||
// TODO: 프로그램 구현 | ||
LottoController().run() | ||
} |
This file was deleted.
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,86 @@ | ||
package lotto.controller | ||
|
||
import lotto.model.LottoFactory | ||
import lotto.model.LottoResultCalculator | ||
import lotto.util.validator.BonusNumberValidator | ||
import lotto.util.validator.LottoNumberValidator | ||
import lotto.util.validator.PurchaseAmountValidator | ||
import lotto.view.InputView | ||
import lotto.view.OutputView | ||
|
||
class LottoController( | ||
private val inputView: InputView = InputView(), | ||
private val outputView: OutputView = OutputView(), | ||
) { | ||
private lateinit var lottoFactory: LottoFactory | ||
private lateinit var lottoResultCalculator: LottoResultCalculator | ||
private lateinit var winningNumbers: List<Int> | ||
Comment on lines
+15
to
+17
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 요 아이들인 일부러 사용하시는 함수에서만 사용하시려구 지연 초기화 하신걸까요? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저도 이거 좋은 것 같아요! 제 코드에서는 맨날 초기화하라구 떴었는데 ㅎㅎ 배워갑니다 |
||
private var bonusNumber: Int? = null | ||
|
||
fun run() { | ||
payment() | ||
giveLotteries() | ||
inputWinningNumber() | ||
showResult() | ||
} | ||
|
||
private fun payment() { | ||
val purchaseAmount = getValidatedInput( | ||
inputFunction = { inputView.getPurchaseAmount() }, | ||
validationFunction = { PurchaseAmountValidator.getValidatePurchaseAmount(it) } | ||
) | ||
lottoFactory = LottoFactory(purchaseAmount) | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. give lottaries가 무슨 뜻일까요?? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 로또의 복수형을 검색해보다가 lotteries라고 표현하는글을 참고해서 lotteries라고 표기했습니다! |
||
private fun giveLotteries() { | ||
val lottoQuantity = lottoFactory.lottoQuantity | ||
val purchasedLotteries = lottoFactory.createLotto() | ||
lottoResultCalculator = LottoResultCalculator(purchasedLotteries) | ||
|
||
outputView.showLottoQuantity(lottoQuantity) | ||
outputView.showLottoNumbers(purchasedLotteries) | ||
} | ||
|
||
private fun inputWinningNumber() { | ||
winningNumbers = getValidatedInput( | ||
inputFunction = { inputView.getWinningNumber() }, | ||
validationFunction = { LottoNumberValidator.getValidatedWinningNumbers(it) } | ||
) | ||
bonusNumber = getValidatedInput( | ||
inputFunction = { inputView.getBonusNumber() }, | ||
validationFunction = { BonusNumberValidator.getValidatedBonusNumber(it, winningNumbers) } | ||
) | ||
inputView.close() | ||
} | ||
|
||
private fun showResult() { | ||
val lottoResults = lottoResultCalculator.getLottoResults(winningNumbers, bonusNumber!!) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. bonusNumber가 |
||
val purchaseAmount = lottoFactory.getPurchaseAmount() | ||
val yield = lottoResultCalculator.getLottoYield(lottoResults, purchaseAmount) | ||
|
||
outputView.showMatchingLottoAmount(lottoResults) | ||
outputView.showYield(yield) | ||
} | ||
|
||
private fun <T> getValidatedInput( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. validate 함수를 주입하는 방식이 정말 좋은 것 같아요!! 배울 점이 많은 코드인 것 같습니다!! |
||
inputFunction: () -> String, | ||
validationFunction: (String) -> T | ||
): T { | ||
while (true) { | ||
try { | ||
val input = inputFunction() | ||
val validatedInput = validationFunction(input) | ||
return validatedInput | ||
} catch (e: IllegalArgumentException) { | ||
showErrorMessage(e) | ||
} | ||
} | ||
} | ||
|
||
private fun showErrorMessage(e: IllegalArgumentException) { | ||
val errorMessage = e.message | ||
if (errorMessage != null) { | ||
outputView.showErrorMessage(errorMessage) | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package lotto.model | ||
|
||
import lotto.util.validator.LottoNumberValidator | ||
|
||
class Lotto(private val numbers: List<Int>) { | ||
|
||
init { | ||
LottoNumberValidator.validateLottoNumbers(numbers) | ||
} | ||
|
||
fun getLottoNumber(): List<Int> = numbers | ||
|
||
fun getLottoRank(winningNumber: List<Int>, bonusNumber: Int): LottoRank { | ||
val matchedCount = numbers.count { number -> number in winningNumber } | ||
val isBonusNumberMatched = bonusNumber in numbers | ||
val lottoRank = LottoRank.fromMatchedCount(matchedCount, isBonusNumberMatched) | ||
return lottoRank | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
package lotto.model | ||
|
||
import camp.nextstep.edu.missionutils.Randoms | ||
import lotto.util.constant.LottoRules | ||
|
||
class LottoFactory(private val purchaseAmount: Int) { | ||
val lottoQuantity: Int = purchaseAmount / LottoRules.AMOUNT_UNIT | ||
|
||
fun getPurchaseAmount(): Int = purchaseAmount | ||
Comment on lines
+6
to
+9
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. purchaseAmount 매개변수를 private val로 설정해두고 아래에서 따로 public 함수인 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 와 그렇네요? 처음에 |
||
|
||
fun createLotto(): List<Lotto> { | ||
val lottoList: MutableList<Lotto> = mutableListOf() | ||
repeat(lottoQuantity) { | ||
val numbers = createNumber() | ||
val lotto = Lotto(numbers) | ||
lottoList.add(lotto) | ||
} | ||
return lottoList | ||
} | ||
|
||
private fun createNumber(): List<Int> { | ||
val randomNumbers = Randoms.pickUniqueNumbersInRange( | ||
LottoRules.LOTTO_NUMBER_MIN, | ||
LottoRules.LOTTO_NUMBER_MAX, | ||
LottoRules.LOTTO_NUMBER_COUNT | ||
).sorted() | ||
return randomNumbers | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
최대 구매 금액을 10억원으로 설정하신 이유가 있을까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
처음엔 10만원으로 제한을 두려했다가, '로또 발행을 많이 했을때 처리속도가 얼마나 될까?' 라는 단순한 생각으로 21억을 안넘는 이쁜숫자 10억으로 설정했어요!