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

[로또] 이아름 미션 제출합니다. #103

Open
wants to merge 16 commits into
base: main
Choose a base branch
from
Open
74 changes: 74 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,75 @@
# kotlin-lotto-precourse

## 개요

간단한 로또 발매기를 구현

## 기능 요구 사항

- 로또
- 번호의 숫자 범위: 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원
- 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료

- 입력
- 로또 구입 금액을 입력
- 구입 금액
- 1,000원 단위로 입력
- 1,000원으로 나누어 떨어지지 않는 경우 예외 처리
```
14000
```
- 당첨 번호를 입력
- 번호는 쉼표(`,`)를 기준으로 구분
```
1,2,3,4,5,6
```
- 보너스 번호를 입력
```
7
```
- 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException`을 발생시키고, `[ERROR]`로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받음
- `Exception`이 아닌 `IllegalArgumentException`, `IllegalStateException` 등과 같은 명확한 유형 처리
- 출력
- 발행한 로또 수량 및 번호를 출력
- 로또 번호 오름차순으로 정렬
```
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 사이의 숫자여야 합니다.
```
6 changes: 4 additions & 2 deletions 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().start()
}
9 changes: 0 additions & 9 deletions src/main/kotlin/lotto/Lotto.kt

This file was deleted.

60 changes: 60 additions & 0 deletions src/main/kotlin/lotto/controller/LottoController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
package lotto.controller

import lotto.model.Lottoes
import lotto.model.Money
import lotto.model.WinningNumbers
import lotto.view.InputView
import lotto.view.OutputView

class LottoController {
fun start() {
val money = inputPurchasedMoney()
val lottoes = purchaseLottoes(money.countOfPurchasedLotto)
val winningNumbers = inputWinningNumbers()
setBounusNumber(winningNumbers)
val winningStatistic = lottoes.confirmWinningNumbers(winningNumbers)
val earningsRate = money.calculateEarningsRate(winningStatistic)
OutputView.printWinningStatistics(winningStatistic, earningsRate)
}

private fun setBounusNumber(winningNumbers: WinningNumbers) {
while (true) {
try {
return winningNumbers.setBonusNumber(inputBonusNumber())
} catch (e: IllegalArgumentException) {
println(e.message)
}
}

}

private fun inputWinningNumbers(): WinningNumbers {
while (true) {
try {
val winningNumbers = InputView.inputWinningNumbers()
return WinningNumbers(winningNumbers)
} catch (e: IllegalArgumentException) {
println(e.message)
}
}
}

private fun inputBonusNumber() = InputView.inputBonusNumber()

private fun purchaseLottoes(countOfPurchasedLotto: Int): Lottoes {
val lottoes = Lottoes(countOfPurchasedLotto)
OutputView.printCountOfPurchasedLotto(countOfPurchasedLotto)
OutputView.printLottoes(lottoes.lottoes)
return lottoes
}

private fun inputPurchasedMoney(): Money {
while (true) {
try {
return Money(InputView.inputPurchasedMoney())
} catch (e: IllegalArgumentException) {
println(e.message)
}
}
}
}
31 changes: 31 additions & 0 deletions src/main/kotlin/lotto/model/Lotto.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package lotto.model

import lotto.utils.Constants

class Lotto(private val numbers: List<Int>) {
init {
checkNumbersValidate()
}

private fun checkNumbersValidate() {
numbers.forEach { number ->
require(number in lottoRange) { Constants.LOTTO_NUMBER_RANGE_ERROR_MESSAGE }
}
require(numbers.size == 6) { Constants.LOTTO_NUMBER_SIZE_ERROR_MESSAGE }
require(numbers.size == numbers.toSet().size) { Constants.LOTTO_NUMBER_DUPLICATION_ERROR_MESSAGE }
}

fun getMatchCount(winningNumbers: List<Int>): Int {
return numbers.filter { it in winningNumbers }.size
}

fun isMatchNumber(number: Int): Boolean = numbers.contains(number)

override fun toString(): String {
return numbers.sorted().toString()
}

companion object {
val lottoRange = Constants.MIN_LOTTO_NUMBER_RANGE..Constants.MAX_LOTTO_NUMBER_RANGE
}
}
32 changes: 32 additions & 0 deletions src/main/kotlin/lotto/model/Lottoes.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
package lotto.model

import camp.nextstep.edu.missionutils.Randoms
import lotto.utils.Constants

class Lottoes(countOfPurchasedLotto: Int) {
val lottoes = mutableListOf<Lotto>()

init {
for (i in 0 until countOfPurchasedLotto) {
lottoes.add(Lotto(makeLottoNumbers()))
}
}

private fun makeLottoNumbers(): List<Int> {
return Randoms.pickUniqueNumbersInRange(
Constants.MIN_LOTTO_NUMBER_RANGE,
Constants.MAX_LOTTO_NUMBER_RANGE,
Constants.LOTTO_NUMBER_COUNT
)
}

fun confirmWinningNumbers(winningNumbers: WinningNumbers): Map<WinningRank, Int> {
val winningStatistic = HashMap<WinningRank, Int>()
lottoes.forEach { lotto ->
val winningRank = winningNumbers.lottoToWinningRank(lotto)
val countByWinningRank = winningStatistic.getOrDefault(winningRank, Constants.LOTTO_MIN_COUNT)
winningStatistic[winningRank] = countByWinningRank + Constants.LOTTO_COUNT_PLUS_UNIT
}
return winningStatistic
}
}
26 changes: 26 additions & 0 deletions src/main/kotlin/lotto/model/Money.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package lotto.model

import lotto.utils.Constants

class Money(private val purchasedMoney: Int) {
val countOfPurchasedLotto: Int
get() = purchasedMoney / Constants.MONEY_UNIT

init {
require(purchasedMoney >= Constants.MIN_MONEY) { Constants.PURCHASED_MONEY_MIN_ERROR_MESSAGE }
require(purchasedMoney % Constants.MONEY_UNIT == Constants.MIN_MONEY) { Constants.PURCHASED_MONEY_UNIT_ERROR_MESSAGE }
}

fun calculateEarningsRate(winningStatistic: Map<WinningRank, Int>): Float {
val totalWinningMoney = calculateTotalWinningMoney(winningStatistic)
return totalWinningMoney / purchasedMoney * 100
}

private fun calculateTotalWinningMoney(winningStatistic: Map<WinningRank, Int>): Float {
var sum = 0f
winningStatistic.forEach { (winningRank, countByWinningRank) ->
sum += winningRank.prizeMoney * countByWinningRank
}
return sum
}
}
31 changes: 31 additions & 0 deletions src/main/kotlin/lotto/model/WinningNumbers.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package lotto.model

import lotto.utils.Constants

class WinningNumbers(private val winningNumbers: List<Int>) {
private var bonusNumber: Int = Constants.BONUS_NUMBER_INIT

init {
checkNumbersValidate()
}

private fun checkNumbersValidate() {
winningNumbers.forEach { number ->
require(number in Lotto.lottoRange) { Constants.LOTTO_NUMBER_RANGE_ERROR_MESSAGE }
}
require(winningNumbers.size == 6) { Constants.LOTTO_NUMBER_SIZE_ERROR_MESSAGE }
require(winningNumbers.size == winningNumbers.toSet().size) { Constants.LOTTO_NUMBER_DUPLICATION_ERROR_MESSAGE }
}

fun setBonusNumber(bonusNumber: Int) {
require(!winningNumbers.contains(bonusNumber)) { Constants.LOTTO_NUMBER_DUPLICATION_ERROR_MESSAGE }
this.bonusNumber = bonusNumber
}

fun lottoToWinningRank(lotto: Lotto): WinningRank {
val matchCount = lotto.getMatchCount(winningNumbers)
val isBonusMatch = lotto.isMatchNumber(bonusNumber)
return WinningRank.entries.firstOrNull { it.matchCount == matchCount && it.isBonusMatch == isBonusMatch }
?: WinningRank.NONE
}
}
22 changes: 22 additions & 0 deletions src/main/kotlin/lotto/model/WinningRank.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package lotto.model

import lotto.utils.Constants.FIFTH_RANK_MATCH_COUNT
import lotto.utils.Constants.FIFTH_RANK_PRIZE_MONEY
import lotto.utils.Constants.FIRST_RANK_MATCH_COUNT
import lotto.utils.Constants.FIRST_RANK_PRIZE_MONEY
import lotto.utils.Constants.FOURTH_RANK_MATCH_COUNT
import lotto.utils.Constants.FOURTH_RANK_PRIZE_MONEY
import lotto.utils.Constants.NONE_MATCH_COUNT
import lotto.utils.Constants.NONE_PRIZE_MONEY
import lotto.utils.Constants.SECOND_AND_THIRD_RANK_MATCH_COUNT
import lotto.utils.Constants.SECOND_RANK_PRIZE_MONEY
import lotto.utils.Constants.THIRD_RANK_PRIZE_MONEY

enum class WinningRank(val matchCount: Int, val prizeMoney: Int, val isBonusMatch: Boolean) {
NONE(NONE_MATCH_COUNT, NONE_PRIZE_MONEY, false),
FIFTH(FIFTH_RANK_MATCH_COUNT, FIFTH_RANK_PRIZE_MONEY, false),
FOURTH(FOURTH_RANK_MATCH_COUNT, FOURTH_RANK_PRIZE_MONEY, false),
THIRD(SECOND_AND_THIRD_RANK_MATCH_COUNT, THIRD_RANK_PRIZE_MONEY, false),
SECOND(SECOND_AND_THIRD_RANK_MATCH_COUNT, SECOND_RANK_PRIZE_MONEY, true),
FIRST(FIRST_RANK_MATCH_COUNT, FIRST_RANK_PRIZE_MONEY, false)
}
38 changes: 38 additions & 0 deletions src/main/kotlin/lotto/utils/Constants.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package lotto.utils

object Constants {
const val PURCHASED_MONEY_INPUT_MESSAGE = "구입금액을 입력해 주세요."
const val PURCHASED_MONEY_MIN_ERROR_MESSAGE = "[ERROR] 구입금액은 0원 이상이여야 합니다."
const val PURCHASED_MONEY_UNIT_ERROR_MESSAGE = "[ERROR] 구입금액은 1000원 단위여야 합니다."
const val PURCHASED_MONEY_INPUT_DELIMITERS = ","
const val LOTTO_PURCHASED_COUNT_MESSAGE = "\n%d개를 구매했습니다."
const val WINNING_NUMBER_INPUT_MESSAGE = "\n당첨 번호를 입력해 주세요."
const val BONUS_NUMBER_INPUT_MESSAGE = "보너스 번호를 입력해 주세요."
const val WINNING_STATISTICS_INFO_MESSAGE = "당첨 통계\n---"
const val WINNING_STATISTICS_MESSAGE = "%d개 일치 (%s원) - %d개"
const val WINNING_STATISTICS_BONUS_MESSAGE = "%d개 일치, 보너스 볼 일치 (%s원) - %d개"
const val TOTAL_EARNINGS_RATE_MESSAGE = "총 수익률은 %.1f%%입니다."
const val LOTTO_NUMBER_RANGE_ERROR_MESSAGE = "[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다."
const val LOTTO_NUMBER_SIZE_ERROR_MESSAGE = "[ERROR] 로또 번호는 6개의 숫자여야 합니다."
const val LOTTO_NUMBER_TYPE_ERROR_MESSAGE = "[ERROR] 로또 번호는 숫자여야 합니다."
const val LOTTO_NUMBER_DUPLICATION_ERROR_MESSAGE = "[ERROR] 로또 번호는 중복되지 않는 숫자여야 합니다."
const val MONEY_UNIT = 1000
const val MIN_MONEY = 0
const val MIN_LOTTO_NUMBER_RANGE = 1
const val MAX_LOTTO_NUMBER_RANGE = 45
const val LOTTO_NUMBER_COUNT = 6
const val FIRST_RANK_PRIZE_MONEY = 2_000_000_000
const val FIRST_RANK_MATCH_COUNT = 6
const val SECOND_RANK_PRIZE_MONEY = 30_000_000
const val SECOND_AND_THIRD_RANK_MATCH_COUNT = 5
const val THIRD_RANK_PRIZE_MONEY = 1_500_000
const val FOURTH_RANK_PRIZE_MONEY = 50_000
const val FOURTH_RANK_MATCH_COUNT = 4
const val FIFTH_RANK_PRIZE_MONEY = 5_000
const val FIFTH_RANK_MATCH_COUNT = 3
const val NONE_MATCH_COUNT = 0
const val NONE_PRIZE_MONEY = 0
const val LOTTO_MIN_COUNT = 0
const val LOTTO_COUNT_PLUS_UNIT = 1
const val BONUS_NUMBER_INIT = 1
}
24 changes: 24 additions & 0 deletions src/main/kotlin/lotto/view/InputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package lotto.view

import camp.nextstep.edu.missionutils.Console
import lotto.utils.Constants

object InputView {
fun inputPurchasedMoney(): Int {
println(Constants.PURCHASED_MONEY_INPUT_MESSAGE)
return Console.readLine().stringToInt()
}

fun inputWinningNumbers(): List<Int> {
println(Constants.WINNING_NUMBER_INPUT_MESSAGE)
return Console.readLine().trim().split(Constants.PURCHASED_MONEY_INPUT_DELIMITERS).map { it.stringToInt() }
}

fun inputBonusNumber(): Int {
println(Constants.BONUS_NUMBER_INPUT_MESSAGE)
return Console.readLine().stringToInt()
}

private fun String.stringToInt(): Int =
this.toIntOrNull() ?: throw IllegalArgumentException(Constants.LOTTO_NUMBER_TYPE_ERROR_MESSAGE)
}
Loading