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

[로또] 최윤정 미션 제출합니다. #163

Open
wants to merge 44 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
44 commits
Select commit Hold shift + click to select a range
73ca874
[docs] 기능명세 추가
cbj0010 Nov 6, 2023
c9c2b32
[docs] 기능명세 추가 및 수정
cbj0010 Nov 6, 2023
fe34565
[Feat] 1~45개 사이의 6개의 숫자를 생성한다.
cbj0010 Nov 6, 2023
3960686
[docs] 기능명세 수정
cbj0010 Nov 6, 2023
9455d02
[docs] 로또 판매 아주머니 check
cbj0010 Nov 7, 2023
d800a7d
[Feat] 로또 판매 아주머니 check
cbj0010 Nov 7, 2023
f7f0de5
[Feat/Docs] 로또 판매 아주머니 check
cbj0010 Nov 7, 2023
419add9
[Feat] 제대로 된 로또 한장 만들기
cbj0010 Nov 7, 2023
427df81
[docs] 제대로 된 로또를 만드는 조건 check
cbj0010 Nov 7, 2023
7ed8154
[Feat] 로또 아주머니가 원하는 로또만큼 제공해줌
cbj0010 Nov 7, 2023
e98a048
[Feat/docs] 몇개의 로또를 살건지 금액 내기
cbj0010 Nov 7, 2023
743c79a
[Feat/docs] 보너스 숫자 입력받기
cbj0010 Nov 7, 2023
a08cadc
[Feat] 로또 판매하는 가게 생성 및 시작지점 설정
cbj0010 Nov 7, 2023
1cf4da0
[Feat] 로또 생성 조건 체크
cbj0010 Nov 8, 2023
a0957ed
[Feat/docs] 로또 생성 조건 체크
cbj0010 Nov 8, 2023
dda081b
[Feat/docs] 보너스 생성 조건 체크
cbj0010 Nov 8, 2023
8fa4a4f
[style/Refactor] 에러 수정 및 함수 호출 순서 수정
cbj0010 Nov 8, 2023
e84a2f5
[docs] 기능명세 체크
cbj0010 Nov 8, 2023
3f077a5
[Feat] 로또 금액 입력받기
cbj0010 Nov 8, 2023
10f87f4
[Feat] 보너스 값 출력
cbj0010 Nov 8, 2023
2dc93af
[Feat] 로또의 당첨 수를 알 수 있게 생성
cbj0010 Nov 8, 2023
039a4e1
[Feat] lotto의 번호를 비교하는 곳
cbj0010 Nov 8, 2023
6bd095f
[Refactor] 변수명 수정
cbj0010 Nov 8, 2023
12ab40f
[Feat] 로또 리스트와 사용자가 입력한 값 비교 몇등인지 카운트
cbj0010 Nov 8, 2023
6454668
[docs] 로또를 비교할 수 있는 기능 check
cbj0010 Nov 8, 2023
3ac315d
[Feat] 출력문 보여주는 view 분리
cbj0010 Nov 8, 2023
d6f5faa
[Feat] 수익률 계산
cbj0010 Nov 8, 2023
6543ea8
[Refactor] 생성자 파라미터 위치 수정
cbj0010 Nov 8, 2023
906af98
[Feat/docs] 수익률 계산
cbj0010 Nov 8, 2023
f7dc8a8
[Feat] 출력함수 생성
cbj0010 Nov 8, 2023
f7d851d
[Test] LottoTest 범위 예외 추가
cbj0010 Nov 8, 2023
3eb20bb
[Feat/docs] 일치하는 수에 맞게 당첨금 수령 check
cbj0010 Nov 8, 2023
ce80548
[Feat] 몇개가 당첨되었는지
cbj0010 Nov 8, 2023
5aed29f
[Refactor] 생성자 인자로 받기
cbj0010 Nov 8, 2023
f6ddf84
[Refactor] 문구 수정
cbj0010 Nov 8, 2023
f43cb36
[Refactor] 파라미터 수정
cbj0010 Nov 8, 2023
930f327
[docs] 당첨 횟수 확인 check
cbj0010 Nov 8, 2023
c6b6e3d
[Test] 제대로 된 돈을 입력하였는지
cbj0010 Nov 8, 2023
109b613
[Test] 등수가 제대로 출력이 되는지
cbj0010 Nov 8, 2023
d3b7666
[Refactor] 안쓰는 변수 삭제
cbj0010 Nov 8, 2023
a5e9ae4
[Refactor] 보너스 번호 입력 수정
cbj0010 Nov 8, 2023
0bc53ec
[Test] 보너스 번호가 범위 밖일 때
cbj0010 Nov 8, 2023
38dc93a
[Test] 보너스가 받은 값과 중복일 때
cbj0010 Nov 8, 2023
f65db78
[style] 나만 보기 위한 주석 삭제
cbj0010 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
107 changes: 107 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
## 공통 피드백

- readme를 상세하게 적는다 - readme는 언제든 바뀌어도 된다. 계속 수정하며 작업하라.

해당 프로젝트에 대한 설명, 기능명세

- 기능명세에는 구현해야 할 기능 목록을 정리하는데 초점을 맞춘다.

예외적인 상황도 기능 목록에 정리한다. (구현을 하면서 계속 추가한다)

- 값을 하드 코딩하지 않는다.

상수화 해라

- 구현 순서도 코딩 컨벤션이다.
- 변수명에 자료형 쓰지 마라 carList → cars
- 하나의 함수는 한 가지 기능만 담당해라.(15줄을 넘지 않도록 한다.)
- 작은 단위의 테스트부터 만든다.

## 추가된 요구사항

- 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다.
- 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다.
- else를 지양한다.
- 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
- 때로는 if/else, when문을 사용하는 것이 더 깔끔해 보일 수 있다. 어느 경우에 쓰는 것이 적절할지 스스로 고민해 본다.
- Enum 클래스를 적용해 프로그래밍을 구현한다.
- 도메인 로직에 단위 테스트를 구현해야 한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다.
- 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 분리해 구현한다.
- 단위 테스트 작성이 익숙하지 않다면 `test/kotlin/lotto/LottoTest`를 참고하여 학습한 후 테스트를 구현한다.
- 에러 문구는 "[ERROR]"로 시작해야 한다.

# 기능명세

쉽게 풀어쓰는 로또 이야기

## 로또 이야기

- 1등을 오백만명 배출한 로또지점에 오신걸 환영합니다~
- 나는 오늘 로또를 10장 구매해야지!! 나 꼭 1등 당첨될거야!
- 나 로또 10장 사야겠다. 음 그럼 얼마 필요하지?
- 로또 한장에 1000원이네? 그러면 10000원 필요하겠다!
- 아주머니~ 로또 10장 주세요!
- 10000원 지불하고 로또 10장 받기 성공~!
- 나는 당첨번호가 {x,x,x,x,x,x}이렇게랑 보너스는 Y가 나올거 같아!
내가 받은 로또 10장을 한번 긁어볼까?
어? 당첨번호가?̊̈-?̊̈
- 1등 → 헉? 당첨번호가 다 맞잖아?
- 2등 → 헉? 당첨번호 5개랑 보너스 번호가 맞잖아?
- 3등 → 헉? 당첨번호 5개가 맞잖아?
- 4등 → 힝 ㅠ 당첨번호 4개가 맞네
- 5등 → 그래도 당첨번호 3개나 맞았네!
- 10장의 당첨이 어떻게 되었지? 한번 나열해볼까?
- 음 당첨 수익률이 어캐되징 함 보여줘바! 오오~!

로또구입 -> 구입한 로또 개수 조회 -> 구입한 로또 조회 -> 종료

## User

- [x] 몇개의 로또를 살 것 인지 로또 판매 아줌씨한테 알려준다.
- [x] 로또 번호를 입력받는다. - inputMoney()
- [x] 당첨번호를 입력한다.- inputLottoNumber()
- [x] 당첨번호는 ,를 기준으로 나눈다

- [x] 보너스 번호를 입력한다. - inputBonusNum()
- [x] 보너스 번호는 1개이며 1~45 사이의 숫자여야한다.
- [x] 먼저 입력한 당첨 번호와 중복되면 안된다.

## 로또를 판매하는 아주머니

- [x] 로또 수량을 체크한다.
- [x] 구입 금액은 1,000원 단위이며, 1,000원으로 나누어 떨어지지 않는 경우 예외 처리한다.
- [x] 로또 공장에서 고객이 요구한 로또만큼 가져와서 준다.

## Lotto

- [x] 당첨번호는 6개이다.
- [x] 1~45 사이의 숫자여야 한다.
- [x] 중복 숫자는 없어야 한다.
- [X] 로또 번호는 오름차순으로 정리되어야 한다.
- [x] ,으로 구별한다.
- [x] 위의 조건을 지키지 않은 경우 예외를 발생시킨 후 다시 당첨번호를 입력받게 한다.

## LottoFactory

- [x] 1~45개 사이의 6개의 숫자를 생성한다.
- [x] 중복 숫자는 없어야 한다.
- [x] 중복 발생시 다시 숫자 랜덤뽑게 하기
- [x] 로또 번호는 오름차순으로 정리되어야 한다.

### Compare

- [x] 손님과 랜덤 로또 번호를 비교할 수 있다. compare()
- [x] 로또가 같은 숫자를 포함하고 있으면 당첨이다. checkSameNumber()
- [x] 3개번호 일치 - 5000원
- [x] 4개 번호 일치 - 50000원
- [x] 5개 번호 일치 - 1500000
- [x] 5개번호+ 보너스 번호 일치 - 3000000
- [x] 6개 번호 일치 1등 - 2000000000

## WinningNumberChecker

- [x] 몇개가 당첨 되었는지 알 수 있다.

## **YieldCalculator**

- [x] 수익률을 계산할 수 있다.
2 changes: 1 addition & 1 deletion src/main/kotlin/lotto/Application.kt
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package lotto

fun main() {
TODO("프로그램 구현")
LottoStore().startSellLotto()
}
43 changes: 43 additions & 0 deletions src/main/kotlin/lotto/Calculator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
package lotto

import lotto.LottoSeller.Companion.LOTTO_TICKET_PRICE

class Calculator {
val lottoResult = mutableMapOf<MatchedCount, Int>(
MatchedCount.FIFTH to 0,
MatchedCount.FOURTH to 0,
MatchedCount.THIRD to 0,
MatchedCount.SECOND to 0,
MatchedCount.FIRST to 0,
)
private var profitability = 0L
private var lottoTicketCount = 0
fun compareNum(
userLotto: List<Int>,
bonusNum: Int,
lottoMachine: List<List<Int>>
) {
lottoTicketCount = lottoMachine.size
for (lotto in lottoMachine) {

val matchedNumbers = lotto.intersect(userLotto.toSet()).size
val isBonusMatched = userLotto.contains(bonusNum) //보너스 볼이 포함 되어 있는지 확인
val lottoRank = MatchedCount.fromMatchedNumbers(matchedNumbers, isBonusMatched)
if (lottoRank != MatchedCount.NONE) {
lottoResult[lottoRank] = lottoResult.getOrDefault(lottoRank, 0) + 1
}
}
}

fun calculateProfitRate(): Float {
calculateProfit()
val moneySpent = lottoTicketCount * LOTTO_TICKET_PRICE
return (profitability * 100f) / moneySpent
}

private fun calculateProfit() {
lottoResult.forEach { (rank, count) ->
profitability += rank.prize * count
}
}
}
27 changes: 25 additions & 2 deletions src/main/kotlin/lotto/Lotto.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,31 @@ package lotto

class Lotto(private val numbers: List<Int>) {
init {
require(numbers.size == 6)
require(numbers.size == DEFAULT_LOTTO_SIZE)
checkLottoNumberException()
}

// TODO: 추가 기능 구현
fun checkLottoNumberException(): List<Int> {
if (numbers.size != 6) throw IllegalArgumentException(ERROR_INPUT_NUMBER_LENGTH)
if (numbers.distinct().size != 6) throw IllegalArgumentException(ERROR_INPUT_NUMBER_DISTINCT)
isAllNumbersInRange(numbers)
return numbers.sorted()
}

private fun isAllNumbersInRange(list: List<Int>): Boolean {
for (number in list) {
if (number < 1 || number > 45) {
throw IllegalArgumentException(ERROR_INPUT_NUMBER_RANGE)
}
}
return true
}


companion object {
private const val DEFAULT_LOTTO_SIZE = 6
private const val ERROR_INPUT_NUMBER_LENGTH = "[ERROR] 6개의 숫자만 입력 가능합니다"
private const val ERROR_INPUT_NUMBER_DISTINCT = "1[ERROR]수 중에 중복이 있습니다."
private const val ERROR_INPUT_NUMBER_RANGE = "[ERROR]1~45사이의 수가 아닙니다"
}
}
44 changes: 44 additions & 0 deletions src/main/kotlin/lotto/LottoGameView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package lotto

class LottoGameView {

fun startInputLottoMoney() {
println(INPUT_MONEY)
}

fun inputUserLottoNumber() {
println(INPUT_LOTTO_NUMBERS)
}

fun inputBonusNumber() {
println(INPUT_BONUS)
}

fun printMatchedNumbersCount(lottoResult: Calculator) {
println(SHOW_MATCH_PROFIT)
println(SHOW_PROFIT_LINE)
lottoResult.lottoResult.forEach { (rank, count) ->
val prize = rank.prize
println("${rank.statement} (${prize.toDecimalFormat()}$MONEY_WON) - ${count}$COUNT")
}
}

fun Long.toDecimalFormat(): String {
return String.format("%,d", this)
}

fun printProfitRate(profitRate: Float) {
println("$ALL_PRICE_RATE ${"%.1f".format(profitRate)}%입니다.")
}

companion object {
private const val INPUT_MONEY = "구입 금액을 입력해주세요"
private const val INPUT_LOTTO_NUMBERS = "당첨번호를 입력해주세요"
private const val INPUT_BONUS = "보너스 번호를 입력해주세요"
private const val SHOW_MATCH_PROFIT = "당첨 통계"
private const val SHOW_PROFIT_LINE = "---"
private const val MONEY_WON = "원"
private const val COUNT = "개"
private const val ALL_PRICE_RATE = "총 수익률은"
}
}
26 changes: 26 additions & 0 deletions src/main/kotlin/lotto/LottoNumberGenerator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package lotto

import camp.nextstep.edu.missionutils.Randoms

class LottoNumberGenerator : NumberGenerator {

override fun generate(): List<Int> {
val randomLottoNumber = Randoms.pickUniqueNumbersInRange(MIN_NUMBER, MAX_NUMBER, PICK_NUMBER)
return checkRandomLottoNumber(randomLottoNumber)
}

private fun checkRandomLottoNumber(randomNum: List<Int>): List<Int> {
if (randomNum.distinct().size > 6) generate()
return isSortedLottoNumber(randomNum)
}

private fun isSortedLottoNumber(randomNum: List<Int>): List<Int> {
return randomNum.sorted()
}

companion object {
const val MIN_NUMBER = 1
const val MAX_NUMBER = 45
const val PICK_NUMBER = 6
}
}
73 changes: 73 additions & 0 deletions src/main/kotlin/lotto/LottoSeller.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package lotto


class LottoSeller() {
fun generateLottoNumbers(lottoMoney: Int): List<LottoNumberGenerator> {
val result = mutableListOf<LottoNumberGenerator>()
repeat(lottoMoney) {
result.add(LottoNumberGenerator())
}
return result
}

fun isValidateLotto(): List<Int> {
return try {
Lotto(User().inputLottoNumber()).checkLottoNumberException()
} catch (e: IllegalArgumentException) {
checkExceptionStatement(e)
LottoSeller().isValidateLotto()
}
}

private fun checkExceptionStatement(e: IllegalArgumentException) {
when (e.message) {
ERROR_INPUT_NUM_LENGTH -> println(ERROR_INPUT_NUM_LENGTH_PRINT)
ERROR_INPUT_NUM -> println(ERROR_INPUT_NUM_PRINT)
else -> println(ERROR_TRY_AGAIN_NUM_PRINT)
}
}

fun checkLottoTicketCount(lottoMoney: String): Int {
lottoMoney.forEach { char ->
val charConvertedToCode = char.code
if ((charConvertedToCode > 57) or (charConvertedToCode < 48)) {
throw IllegalArgumentException(ERROR_ONLY_INPUT_INT)
}
}
if (lottoMoney.toInt() % LOTTO_TICKET_PRICE != LOTTO_TICKET_REMAINDER) {
throw IllegalArgumentException(ERROR_INPUT_MONEY)
}

return lottoMoney.toInt() / LOTTO_TICKET_PRICE
}

fun checkLottoHasBonusNum(lottoList: List<Int>, bonusNum: Int): Int {
checkContainNum(lottoList, bonusNum)
checkBonusLength(bonusNum)
return bonusNum
}

private fun checkContainNum(lottoList: List<Int>, bonusNum: Int): Int {
if (lottoList.contains(bonusNum)) throw IllegalArgumentException(ERROR_INPUT_NUMBER_DISTINCT)
return bonusNum
}

private fun checkBonusLength(bonusNum: Int): Int {
if (bonusNum !in 1..45) throw IllegalArgumentException(ERROR_INPUT_NUMBER_RANGE)
return bonusNum
}

companion object {
const val LOTTO_TICKET_PRICE = 1000
const val LOTTO_TICKET_REMAINDER = 0
private const val ERROR_INPUT_MONEY = "[ERROR]로또 한장은 1,000원 입니다. 다시 입력해주세요"
private const val ERROR_ONLY_INPUT_INT = "[ERROR] 정수만 입력 가능합니다"
private const val ERROR_INPUT_NUM_LENGTH = "For input string: \"\""
private const val ERROR_INPUT_NUM_LENGTH_PRINT = "[ERROR]번호 개수를 체크해주세요"
private const val ERROR_INPUT_NUM = "Failed requirement."
private const val ERROR_INPUT_NUM_PRINT = "[ERROR]잘못된 번호를 입력했습니다."
private const val ERROR_TRY_AGAIN_NUM_PRINT = "[ERROR]당첨번호를 다시 입력해주세요"
private const val ERROR_INPUT_NUMBER_RANGE = "[ERROR]1~45사이의 수가 아닙니다"
private const val ERROR_INPUT_NUMBER_DISTINCT = "[ERROR] 이미 입력한 번호 입니다."
}
}
47 changes: 47 additions & 0 deletions src/main/kotlin/lotto/LottoStore.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
package lotto

class LottoStore {
private val randomLottos = mutableListOf<List<Int>>()
private val lottoGameView = LottoGameView()
fun startSellLotto() {
lottoGameView.startInputLottoMoney()
val seller = LottoSeller().generateLottoNumbers(userInput(User().inputMoney()))
showRandomLotto(seller)
lottoGameView.inputUserLottoNumber()
val lottoNumbers = LottoSeller().isValidateLotto()
lottoGameView.inputBonusNumber()
val lottoSeller = LottoSeller()
val bonusNum: Int = try {
lottoSeller.checkLottoHasBonusNum(lottoNumbers, User().inputBonusNum())
} catch (e: IllegalArgumentException) {
println(e.message)
lottoSeller.checkLottoHasBonusNum(lottoNumbers, User().inputBonusNum())
}
resultLotto(lottoNumbers, bonusNum)
}

private fun userInput(money: String): Int {
return try {
LottoSeller().checkLottoTicketCount(money)
} catch (e: IllegalArgumentException) {
println(e.message)
userInput(User().inputMoney())
}
}

private fun showRandomLotto(randomLotto: List<LottoNumberGenerator>) {
println("${randomLotto.size}개를 구매했습니다.")
for (lotto in randomLotto) {
val machineLottoNumbers = lotto.generate()
println(machineLottoNumbers)
randomLottos.add(machineLottoNumbers)
}
}

private fun resultLotto(lottoNumbers: List<Int>, isBonusValid: Int) {
val lottoResult = Calculator()
lottoResult.compareNum(lottoNumbers, isBonusValid, randomLottos)
LottoGameView().printMatchedNumbersCount(lottoResult)
LottoGameView().printProfitRate(lottoResult.calculateProfitRate())
}
}
Loading