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

[로또] 이홍진 미션 제출합니다. #84

Open
wants to merge 41 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
41 commits
Select commit Hold shift + click to select a range
c6a5be2
docs: README 작성
hongmyeoun Oct 31, 2024
6c413a6
feat: 구입 금액, 로또 발행 기능 구현
hongmyeoun Nov 1, 2024
05b9918
docs: 구입 금액, 로또 발행 기능목록 구현으로 인한 변경
hongmyeoun Nov 1, 2024
56a393d
feat: 당첨 번호 확인 기능 구현
hongmyeoun Nov 1, 2024
0399497
feat: 로또 당첨번호 입력 및 보너스 번호 입력 기능 구현
hongmyeoun Nov 1, 2024
5097ba8
feat: 로또 발행시 로또 숫자를 오름차순으로 정렬하는 기능 추가
hongmyeoun Nov 1, 2024
2575435
feat: 로또 당첨 결과와 수익률 계산기능 구현
hongmyeoun Nov 1, 2024
819399e
feat: 결과값 OutputView 기능 구현
hongmyeoun Nov 1, 2024
0aa480e
docs: 기능 목록 업데이트
hongmyeoun Nov 1, 2024
604ae98
fix: 로또의 수익률 계산공식 및 메서드명 수정
hongmyeoun Nov 4, 2024
e74d8d6
fix: 로또의 수익률 메서드명 수정
hongmyeoun Nov 4, 2024
4f8f106
fix: 검증 함수 분리
hongmyeoun Nov 4, 2024
dbc4e21
fix: 검증 함수 분리 및 메서드 추가
hongmyeoun Nov 4, 2024
a47d1ea
fix: import 제거
hongmyeoun Nov 4, 2024
a686edc
feat: 컨트롤러 생성 및 메인함수 변경
hongmyeoun Nov 4, 2024
20c3657
move: inputView에 const 분리
hongmyeoun Nov 4, 2024
c767fd4
move: Validator를 분리하여 관리
hongmyeoun Nov 4, 2024
3e46f0d
feat: 기능별 고정값 상수처리
hongmyeoun Nov 4, 2024
f6fa329
fix: 상수값처리 및 메서드와 변수 이름 변경
hongmyeoun Nov 4, 2024
90ddcfd
fix: 에러메세지 수정 및 공백 수정
hongmyeoun Nov 4, 2024
642c483
docs: 오타 수정 및 수익률 계산 공식 추가
hongmyeoun Nov 4, 2024
17aa913
feat: 로또 등수를 관리하는 LottoRank enum class 추가
hongmyeoun Nov 4, 2024
e0ad875
fix: LottoRank의 추가로 인한 코드 리팩토링
hongmyeoun Nov 4, 2024
1bcf0d9
feat: 당첨 로또 개수 표기방식 변경
hongmyeoun Nov 4, 2024
896a041
docs: 테스트 일정 변경
hongmyeoun Nov 4, 2024
3054191
test: Lotto class에 대한 테스트
hongmyeoun Nov 4, 2024
43ac563
fix: 코드 구조 리팩토링
hongmyeoun Nov 4, 2024
ec54bce
fix: 코드 구조 리팩토링 및 주석 제거
hongmyeoun Nov 4, 2024
dc3ffc7
test: LottoFactory 테스트
hongmyeoun Nov 4, 2024
fd64384
test: LottoResultCalculator 테스트
hongmyeoun Nov 4, 2024
a9dc229
test: PurchaseAmountValidator 테스트
hongmyeoun Nov 4, 2024
2db253f
fix: PurchaseAmountValidatorTest 명사형 이름에서 명령형으로 변경
hongmyeoun Nov 4, 2024
87c93f3
test: LottoNumberValidator 테스트 추가
hongmyeoun Nov 4, 2024
cd3c575
test: LottoNumberValidator 테스트 수정
hongmyeoun Nov 4, 2024
278f53b
test: LottoNumberValidator 테스트 수정
hongmyeoun Nov 4, 2024
09aa942
test: BonusNumberValidator 테스트 추가
hongmyeoun Nov 4, 2024
6defc69
docs: 테스트 추가 체크
hongmyeoun Nov 4, 2024
acbefab
move: 위치 이동으로 인한 삭제
hongmyeoun Nov 4, 2024
1af1e9b
test: ApplicationTest 추가
hongmyeoun Nov 4, 2024
6242b23
ref: 총 상금 계산 로직 변경
hongmyeoun Nov 4, 2024
6a2f59a
docs: 프로젝트 구조 추가
hongmyeoun Nov 4, 2024
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
216 changes: 215 additions & 1 deletion README.md
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억원 이하여야 합니다."

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

최대 구매 금액을 10억원으로 설정하신 이유가 있을까요?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

처음엔 10만원으로 제한을 두려했다가, '로또 발행을 많이 했을때 처리속도가 얼마나 될까?' 라는 단순한 생각으로 21억을 안넘는 이쁜숫자 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] 자연수가 아닐 경우
4 changes: 3 additions & 1 deletion src/main/kotlin/lotto/Application.kt
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()
}
9 changes: 0 additions & 9 deletions src/main/kotlin/lotto/Lotto.kt

This file was deleted.

86 changes: 86 additions & 0 deletions src/main/kotlin/lotto/controller/LottoController.kt
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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

요 아이들인 일부러 사용하시는 함수에서만 사용하시려구 지연 초기화 하신걸까요?
너무 좋은 방법이네요 !

Choose a reason for hiding this comment

The 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)
}

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

give lottaries가 무슨 뜻일까요??

Copy link
Author

Choose a reason for hiding this comment

The 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!!)

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

bonusNumber가 bonusNumber: Int? = null로 nullable하게 정의되어 있는데 여기에선 !!로 설정을 해두셔서 처음부터 null을 허용하지 않도록 만드는 게 더 안전할 것 같습니다

val purchaseAmount = lottoFactory.getPurchaseAmount()
val yield = lottoResultCalculator.getLottoYield(lottoResults, purchaseAmount)

outputView.showMatchingLottoAmount(lottoResults)
outputView.showYield(yield)
}

private fun <T> getValidatedInput(
Copy link

Choose a reason for hiding this comment

The 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)
}
}
}
19 changes: 19 additions & 0 deletions src/main/kotlin/lotto/model/Lotto.kt
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
}
}
29 changes: 29 additions & 0 deletions src/main/kotlin/lotto/model/LottoFactory.kt
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

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

purchaseAmount 매개변수를 private val로 설정해두고 아래에서 따로 public 함수인 getPurchaseAmount() 으로 만들어서 반환하도록 만드신 이유가 있을까요?
매개변수를 public으로 두면 굳이 함수를 만들지 않아도 되지않을까 싶어서요!

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

와 그렇네요? 처음에 Lotto 클래스와 비슷하게 만들다 보니까 private를 건들면 안된다고 생각한거 같습니다..


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
}
}
Loading