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

[로또] 양민석 미션 제출합니다. #96

Open
wants to merge 24 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
ddad8d7
docs(README): 기능 목록 작성
minseok0416 Nov 4, 2024
e1cdcc3
feat(Lotto): 당첨 번호에 중복된 숫자가 포함된 경우 에러를 발생시키는 기능 추가
minseok0416 Nov 4, 2024
44f3be3
feat(Lotto): 로또 번호의 숫자 범위를 초과한 경우 에러를 발생시키는 기능 추가
minseok0416 Nov 4, 2024
33b985e
feat(Lotto): 구입한 만큼의 로또 발행하는 기능 추가
minseok0416 Nov 4, 2024
44b8490
feat(InputView): 로또 구입 금액을 입력 받는 기능 추가
minseok0416 Nov 4, 2024
e9a1188
feat(InputView): 로또 구입 금액을 입력 받는 함수에 출력문 추가
minseok0416 Nov 4, 2024
1d00c62
feat(InputView): 당첨 번호를 입력 받는 기능 추가
minseok0416 Nov 4, 2024
1c591ee
feat(InputView): 보너스 번호를 입력 받는 기능 추가
minseok0416 Nov 4, 2024
2a6b544
refactor(InputView): 가이드 메세지 상수화
minseok0416 Nov 4, 2024
20d7807
feat(inputView): 각각의 입력 문자열을 Int, List<Int>로 변환하는 기능 추가
minseok0416 Nov 4, 2024
97e86da
refactor(inputView): 입력받는 함수와 변환하는 함수 합침
minseok0416 Nov 4, 2024
1597842
feat(Lotto): 당첨내역을 산정하는 기능 추가
minseok0416 Nov 4, 2024
b1050a8
feat(Lotto): 수익률을 산정하는 기능 추가
minseok0416 Nov 4, 2024
247ce87
feat(OutputView): 발행한 로또 수량 및 번호를 출력하는 기능 추가
minseok0416 Nov 4, 2024
e62e968
refactor(Lotto): 구입한 만큼의 로또를 발행하는 기능 companion object로 분리 및 오름차순으로 정렬
minseok0416 Nov 4, 2024
a998ccf
feat(OutputView): 당첨 내역을 출력하는 기능 추가
minseok0416 Nov 4, 2024
c584f24
feat(OutputView): 수익률을 출력하는 기능 추가
minseok0416 Nov 4, 2024
cad700d
feat(Validator): 잘못된 로또 구입 금액 입력 시 에러를 발생시키는 기능 추가
minseok0416 Nov 4, 2024
e747eb7
feat(Validator): 잘못된 로또 번호 입력 시 에러를 발생시키는 기능 추가
minseok0416 Nov 4, 2024
11fa7fd
refactor(Lotto): init문에 작성한 잘못된 로또 번호를 입력 시 에러를 발생시키는 기능 삭제
minseok0416 Nov 4, 2024
421bce1
feat(InputView): 에러 발생 시 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는 기능 추가
minseok0416 Nov 4, 2024
cf89e5b
refactor(Lotto): init 블록 복구 및 구입한 만큼의 로또 발행하는 테스트 주석 처리
minseok0416 Nov 4, 2024
36ed129
feat(Application): 메인 메서드 구현
minseok0416 Nov 4, 2024
6afe84a
refactor(LottoMachine): main메서드 내용 LottoMachine으로 이동
minseok0416 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
54 changes: 53 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,53 @@
# kotlin-lotto-precourse
# 프리코스 2주차 미션 - 로또

## 기능 요구 사항

간단한 로또 발매기를 구현한다.

- 로또 번호의 숫자 범위는 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]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다.
- `Exception`이 아닌`IllegalArgumentException`,`IllegalStateException`등과 같은 명확한 유형을 처리한다.

## 기능 목록
- 입력
- [ ] **로또 구입 금액**을 입력 받는다.
- [ ] **당첨 번호**를 입력 받는다. 번호는 쉼표(,)를 기준으로 구분한다.
- [ ] **보너스 번호**를 입력 받는다.


- 출력
- [ ] 발행한 로또 **수량** 및 **번호**를 출력한다. 로또 번호는 **오름차순**으로 정렬하여 보여준다.
- [ ] **당첨 내역**을 출력한다.
- [ ] **수익률**을 출력한다. 수익률은 소수점 둘째 자리에서 반올림한다. (ex. 100.0%, 51.5%, _1,000,000.0%_)


- 입력한 값이 유효한지 검증한다.(로또 구입 금액, 당첨 번호, 보너스 번호)
- [ ] 로또 구입 금액이 1000원 미만인 경우
- [ ] 로또 구입 금액이 1000원으로 나누어 떨어지지 않는 경우
- [ ] 로또 번호의 숫자 범위를 초과한 경우(1 ~ 45)
- [ ] 로또 번호의 갯수(당첨 번호 6개, 보너스 번호 1개)를 만족하지 않는 경우
- [ ] 당첨 번호에 중복된 숫자가 포함된 경우
- [ ] 미입력
- [ ] 공백이 포함된 경우
- [ ] 숫자(양수)와 쉼표가 아닌 값이 입력 된 경우


- 구입한 만큼의 로또를 발행한다.


- 당첨 내역을 산정한다.


- 수익률을 산정한다.
4 changes: 2 additions & 2 deletions 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: 프로그램 구현
}
LottoMachine().run()
}
30 changes: 30 additions & 0 deletions src/main/kotlin/lotto/InputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package lotto

import camp.nextstep.edu.missionutils.Console

class InputView {

fun getPurchaseAmount(): Int {
return tryGetInput("구입금액을 입력해 주세요.") { Validator.validatePurchaseAmount(it) }
}

fun getLottoNumbers(): List<Int> {
return tryGetInput("\n당첨 번호를 입력해 주세요.") { Validator.validateLottoNumbers(it) }
}

fun getBonusNumber(): Int {
return tryGetInput("\n보너스 번호를 입력해 주세요.") { Validator.validateBonusNumber(it) }
}

private fun <T> tryGetInput(message: String, validator: (String) -> T): T {
while (true) {
println(message)
val input = Console.readLine()
try {
return validator(input)
} catch (e: IllegalArgumentException) {
println(e.message)
}
}
}
}
42 changes: 41 additions & 1 deletion src/main/kotlin/lotto/Lotto.kt
Original file line number Diff line number Diff line change
@@ -1,9 +1,49 @@
package lotto

import camp.nextstep.edu.missionutils.Randoms

class Lotto(private val numbers: List<Int>) {
var firstRank = 0
var secondRank = 0
var thirdRank = 0
var fourthRank = 0
var fifthRank = 0

init {
require(numbers.size == 6) { "[ERROR] 로또 번호는 6개여야 합니다." }
require(numbers.toSet().size == 6) { "[ERROR] 로또 번호는 중복일 수 없습니다." }
require(numbers.all { it in 1..45 }) { "[ERROR] 로또 번호의 범위는 1 ~ 45입니다." }
}


companion object {
fun issueLotto(count: Int): List<List<Int>> {
return (1..count).map {
Randoms.pickUniqueNumbersInRange(1, 45, 6).sorted()
}
}
}

// TODO: 추가 기능 구현
fun getLottoRank(myLotto: List<List<Int>>, bonusNumber: Int) {
myLotto.forEach { lottoNumbers ->
val matchCount = countMatchingNumbers(lottoNumbers, numbers.sorted())
when {
matchCount == 6 -> firstRank++
matchCount == 5 && lottoNumbers.contains(bonusNumber) -> secondRank++
matchCount == 5 -> thirdRank++
matchCount == 4 -> fourthRank++
matchCount == 3 -> fifthRank++
}
}
}

private fun countMatchingNumbers(list1: List<Int>, list2: List<Int>): Int {
return list1.intersect(list2.toSet()).size
}

fun calculateProfitRate(purchaseAmount: Int): Double {
val totalPrizeMoney =
5000 * fifthRank + 50000 * fourthRank + 1500000 * thirdRank + 30000000 * secondRank + 2000000000 * firstRank
return (totalPrizeMoney / purchaseAmount.toDouble()) * 100
}
}
24 changes: 24 additions & 0 deletions src/main/kotlin/lotto/LottoMachine.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package lotto

class LottoMachine {
fun run() {
val inputView = InputView()
val outputView = OutputView()

val purchaseAmount = inputView.getPurchaseAmount()
val lottoCount = purchaseAmount / 1000

val myLotto = Lotto.issueLotto(lottoCount)
outputView.printLottoCount(lottoCount, myLotto)

val winningNumbers = inputView.getLottoNumbers()
val bonusNumber = inputView.getBonusNumber()

val lotto = Lotto(winningNumbers)
lotto.getLottoRank(myLotto, bonusNumber)
outputView.printLottoRank(lotto)

val profitRate = lotto.calculateProfitRate(purchaseAmount)
outputView.printProfitRate(profitRate)
}
}
24 changes: 24 additions & 0 deletions src/main/kotlin/lotto/OutputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package lotto

class OutputView {
fun printLottoCount(count: Int, myLotto: List<List<Int>>) {
println("\n${count}개를 구매했습니다.")
myLotto.forEach { lottoNumbers ->
println(lottoNumbers.joinToString(prefix = "[", postfix = "]", separator = ", "))
}
}

fun printLottoRank(lotto: Lotto) {
println("\n당첨 통계")
println("---")
println("3개 일치 (5,000원) - ${lotto.fifthRank}개")
println("4개 일치 (50,000원) - ${lotto.fourthRank}개")
println("5개 일치 (1,500,000원) - ${lotto.thirdRank}개")
println("5개 일치, 보너스 볼 일치 (30,000,000원) - ${lotto.secondRank}개")
println("6개 일치 (2,000,000,000원) - ${lotto.firstRank}개")
}

fun printProfitRate(profitRate: Double) {
println("총 수익률은 ${"%.1f".format(profitRate)}%입니다.")
}
}
56 changes: 56 additions & 0 deletions src/main/kotlin/lotto/Validator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package lotto

class Validator {
companion object {
fun validatePurchaseAmount(amount: String): Int {
val purchaseAmount = parseAmount(amount)
checkAmountIsAboveMinimum(purchaseAmount)
checkAmountIsDivisibleByThousand(purchaseAmount)
return purchaseAmount
}

private fun parseAmount(amount: String): Int {
return amount.toIntOrNull() ?: throw IllegalArgumentException("[ERROR] 구입 금액은 숫자여야 합니다.")
}

private fun checkAmountIsAboveMinimum(amount: Int) {
require(amount >= 1000) { "[ERROR] 구입 금액은 1000원 이상이어야 합니다." }
}

private fun checkAmountIsDivisibleByThousand(amount: Int) {
require(amount % 1000 == 0) { "[ERROR] 구입 금액은 1000원 단위로 나누어 떨어져야 합니다." }
}

fun validateLottoNumbers(input: String): List<Int> {
checkInputNotBlank(input)
val numbers = parseLottoNumbers(input)
checkLottoNumbersAreUnique(numbers)
checkLottoNumbersRange(numbers)
return numbers
}

private fun checkInputNotBlank(input: String) {
require(input.isNotBlank()) { "[ERROR] 로또 번호를 입력해 주세요." }
}

private fun parseLottoNumbers(input: String): List<Int> {
return input.split(",").map { it.trim() }
.map { it.toIntOrNull() ?: throw IllegalArgumentException("[ERROR] 로또 번호는 숫자만 입력할 수 있습니다.") }
}

private fun checkLottoNumbersAreUnique(numbers: List<Int>) {
require(numbers.size == 6) { "[ERROR] 로또 번호는 6개여야 합니다." }
require(numbers.toSet().size == 6) { "[ERROR] 로또 번호는 중복될 수 없습니다." }
}

private fun checkLottoNumbersRange(numbers: List<Int>) {
require(numbers.all { it in 1..45 }) { "[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다." }
}

fun validateBonusNumber(input: String): Int {
checkInputNotBlank(input)
return input.toIntOrNull()?.takeIf { it in 1..45 }
?: throw IllegalArgumentException("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.")
}
}
}
33 changes: 30 additions & 3 deletions src/test/kotlin/lotto/LottoTest.kt
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package lotto

import org.junit.jupiter.api.Test
import camp.nextstep.edu.missionutils.test.Assertions.assertRandomUniqueNumbersInRangeTest
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.assertThrows
import org.junit.jupiter.api.Test

class LottoTest {
@Test
Expand All @@ -11,13 +13,38 @@ class LottoTest {
}
}

// TODO: 테스트가 통과하도록 프로덕션 코드 구현
@Test
fun `로또 번호에 중복된 숫자가 있으면 예외가 발생한다`() {
assertThrows<IllegalArgumentException> {
Lotto(listOf(1, 2, 3, 4, 5, 5))
}
}

// TODO: 추가 기능 구현에 따른 테스트 코드 작성
@Test
fun `로또 번호의 숫자 범위를 초과한 경우(1 ~ 45)`() {
assertThrows<IllegalArgumentException> {
Lotto(listOf(1, 2, 3, 4, 5, 46))
Lotto(listOf(0, 2, 3, 4, 5, 6))
Lotto(listOf(-1, 2, 3, 4, 5, 6))
}
}

// @Test
// fun `구입한 만큼의 로또 발행하기`() {
// val lotto = Lotto(listOf(1, 2, 3, 4, 5, 6))
// assertRandomUniqueNumbersInRangeTest(
// {
// assertThat(lotto.issueLotto(3)).isEqualTo(
// listOf(
// listOf(8, 21, 23, 41, 42, 43),
// listOf(3, 5, 11, 16, 32, 38),
// listOf(7, 11, 16, 35, 36, 44)
// )
// )
// },
// listOf(8, 21, 23, 41, 42, 43),
// listOf(3, 5, 11, 16, 32, 38),
// listOf(7, 11, 16, 35, 36, 44)
// )
// }
}