From 8c097e3d69373bedc52eb951b24f34ac13d7c686 Mon Sep 17 00:00:00 2001 From: ErroredPasta Date: Sat, 4 Nov 2023 09:04:48 +0900 Subject: [PATCH 01/20] =?UTF-8?q?docs:=20README=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit README에 프로젝트 개요, 기능, 사용 방법 작성 --- docs/README.md | 30 ++++++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/docs/README.md b/docs/README.md index e69de29bb..33d89d999 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,30 @@ +# 로또 + +## 프로젝트 개요 + +이 프로젝트는 사용자로 부터 금액을 입력 받아서 가상으로 로또를 무작위로 생성, 사용자가 당첨 번호와 보너스 번호를 입력하여 당첨 결과와 수익률을 계산하는 프로그램입니다. + +## 기능 + +1. **금액 입력** : 사용자가 로또를 구매에 사용할 금액을 입력합니다. + - 금액이 1000원으로 나누어 떨어지도록 입력해야 합니다. + - 금액으로 숫자를 입력해야 합니다. +2. **무작위 로또 생성**: (사용자가 입력한 금액 / 1000)만큼의 로또를 무작위 번호로 생성합니다. + - 생성할 때 정렬된 리스트로 생성 +3. **로또 번호 출력**: 무작위로 생성된 로또 번호들을 오름차순으로 출력합니다, +4. **당첨 번호 입력** : 사용자가 당첨 번호를 입력합니다. + - 입력으로 숫자를 입력해야 합니다. + - 숫자들은 ,로 구분됩니다. + - 숫자를 6개 입력해야 합니다. + - 숫자들은 1 ~ 45 사이의 값이어야 합니다. + - 숫자들중 중복이 존재하지 않아야 합니다. +5. **보너스 번호 입력** : 사용자가 보너스 번호를 입력합니다. + - 입력으로 숫자를 입력해야 합니다. + - 숫자가 1 ~ 45 사이의 값이어야 합니다. + - 숫자가 당첨 번호와 중복이 되면 안됩니다. +6. **당첨 통계 출력** : 생성된 로또 번호와 사용자가 입력한 당첨 번호와 보너스 번호를 이용하여 당첨 통계를 계산하고 출력합니다. +7. **수익률 출력** : 사용자가 입력한 금액과 당첨 통계를 이용하여 수익률을 계산하여 출력합니다. + +## 사용 방법 + +애플리케이션을 실행한 후, 화면의 지시에 따라 금액 및 당첨 번호, 보너스 번호를 입력합니다. From 7f850264474e128dcdf679166e74ab9f736eaa25 Mon Sep 17 00:00:00 2001 From: ErroredPasta Date: Sun, 5 Nov 2023 18:03:03 +0900 Subject: [PATCH 02/20] =?UTF-8?q?feat:=20=EC=9C=A0=EC=A0=80=EC=97=90?= =?UTF-8?q?=EA=B2=8C=20=EB=A1=9C=EB=98=90=EB=A5=BC=20=EA=B5=AC=EB=A7=A4?= =?UTF-8?q?=ED=95=A0=20=EA=B8=88=EC=95=A1=EC=9D=84=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=EB=B0=9B=EB=8A=94=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/lotto/Application.kt | 8 ++++- src/main/kotlin/lotto/LottoSimulator.kt | 15 +++++++++ .../kotlin/lotto/{ => domain/model}/Lotto.kt | 2 +- src/main/kotlin/lotto/domain/model/Money.kt | 16 +++++++++ .../lotto/ui/presenter/LottoPresenter.kt | 26 +++++++++++++++ .../kotlin/lotto/ui/view/ConsoleLottoView.kt | 33 +++++++++++++++++++ src/main/kotlin/lotto/ui/view/LottoView.kt | 12 +++++++ src/test/kotlin/lotto/LottoTest.kt | 1 + 8 files changed, 111 insertions(+), 2 deletions(-) create mode 100644 src/main/kotlin/lotto/LottoSimulator.kt rename src/main/kotlin/lotto/{ => domain/model}/Lotto.kt (83%) create mode 100644 src/main/kotlin/lotto/domain/model/Money.kt create mode 100644 src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt create mode 100644 src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt create mode 100644 src/main/kotlin/lotto/ui/view/LottoView.kt diff --git a/src/main/kotlin/lotto/Application.kt b/src/main/kotlin/lotto/Application.kt index d7168761c..a09e85ae8 100644 --- a/src/main/kotlin/lotto/Application.kt +++ b/src/main/kotlin/lotto/Application.kt @@ -1,5 +1,11 @@ package lotto +import lotto.ui.presenter.LottoPresenter +import lotto.ui.view.ConsoleLottoView + fun main() { - TODO("프로그램 구현") + val view = ConsoleLottoView() + val presenter = LottoPresenter(view = view) + + LottoSimulator(view = view, presenter = presenter).start() } diff --git a/src/main/kotlin/lotto/LottoSimulator.kt b/src/main/kotlin/lotto/LottoSimulator.kt new file mode 100644 index 000000000..992c9442f --- /dev/null +++ b/src/main/kotlin/lotto/LottoSimulator.kt @@ -0,0 +1,15 @@ +package lotto + +import lotto.ui.presenter.LottoPresenter +import lotto.ui.view.LottoView + +class LottoSimulator( + private val view: LottoView, + private val presenter: LottoPresenter +) { + fun start() { + view.bindPresenter(presenter = presenter) + + view.onStart() + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/Lotto.kt b/src/main/kotlin/lotto/domain/model/Lotto.kt similarity index 83% rename from src/main/kotlin/lotto/Lotto.kt rename to src/main/kotlin/lotto/domain/model/Lotto.kt index 5ca00b4e4..82fe0372f 100644 --- a/src/main/kotlin/lotto/Lotto.kt +++ b/src/main/kotlin/lotto/domain/model/Lotto.kt @@ -1,4 +1,4 @@ -package lotto +package lotto.domain.model class Lotto(private val numbers: List) { init { diff --git a/src/main/kotlin/lotto/domain/model/Money.kt b/src/main/kotlin/lotto/domain/model/Money.kt new file mode 100644 index 000000000..24de5a6bc --- /dev/null +++ b/src/main/kotlin/lotto/domain/model/Money.kt @@ -0,0 +1,16 @@ +package lotto.domain.model + +@JvmInline +value class Money(val amount: Int) { + init { + require(amount >= MIN_MONEY_AMOUNT) { MUST_GREATER_THAN_MIN_MONEY_MESSAGE } + require(amount % DIVISION_AMOUNT == 0) { MUST_BE_DIVIDABLE_BY_DIVISION_AMOUNT_MESSAGE } + } + + companion object { + const val MIN_MONEY_AMOUNT = 1_000 + const val DIVISION_AMOUNT = 1_000 + const val MUST_GREATER_THAN_MIN_MONEY_MESSAGE = "${MIN_MONEY_AMOUNT}원 이상의 금액을 입력해주세요." + const val MUST_BE_DIVIDABLE_BY_DIVISION_AMOUNT_MESSAGE = "금액이 ${DIVISION_AMOUNT}원으로 나누어 져야 합니다." + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt b/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt new file mode 100644 index 000000000..984ed2c9a --- /dev/null +++ b/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt @@ -0,0 +1,26 @@ +package lotto.ui.presenter + +import lotto.domain.model.Money +import lotto.ui.view.LottoView + +class LottoPresenter( + private val view: LottoView +) { + // inline class는 lateinit var 불가 + private var _money: Money? = null + private val money get() = requireNotNull(_money) + + fun getMoney() { + view.displayEnterMoney() + + runCatching { + view.getMoney() + }.onSuccess { + _money = it + }.onFailure { error -> + view.displayErrorMessage(message = error.message) + view.displayEnterMoney() + view.getMoney() + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt new file mode 100644 index 000000000..8a2163b97 --- /dev/null +++ b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt @@ -0,0 +1,33 @@ +package lotto.ui.view + +import camp.nextstep.edu.missionutils.Console +import lotto.domain.model.Money +import lotto.ui.presenter.LottoPresenter + +class ConsoleLottoView : LottoView { + private lateinit var presenter: LottoPresenter + + override fun bindPresenter(presenter: LottoPresenter) { + this.presenter = presenter + } + + override fun onStart() { + presenter.getMoney() + } + + override fun displayEnterMoney() { + println(ENTER_MONEY_MESSAGE) + } + + override fun getMoney(): Money = Money(amount = Console.readLine().toInt()) + + override fun displayErrorMessage(message: String?) { + println("$ERROR_MESSAGE_PREFIX ${message ?: DEFAULT_ERROR_MESSAGE}") + } + + companion object { + const val ENTER_MONEY_MESSAGE = "구입금액을 입력해 주세요." + const val ERROR_MESSAGE_PREFIX = "[ERROR]" + const val DEFAULT_ERROR_MESSAGE = "에러가 발생했습니다. 다시 시도해주세요." + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/ui/view/LottoView.kt b/src/main/kotlin/lotto/ui/view/LottoView.kt new file mode 100644 index 000000000..6ca63aefc --- /dev/null +++ b/src/main/kotlin/lotto/ui/view/LottoView.kt @@ -0,0 +1,12 @@ +package lotto.ui.view + +import lotto.domain.model.Money +import lotto.ui.presenter.LottoPresenter + +interface LottoView { + fun bindPresenter(presenter: LottoPresenter) + fun onStart() + fun displayEnterMoney() + fun getMoney(): Money + fun displayErrorMessage(message: String?) +} \ No newline at end of file diff --git a/src/test/kotlin/lotto/LottoTest.kt b/src/test/kotlin/lotto/LottoTest.kt index 11d85ac2c..a629a460d 100644 --- a/src/test/kotlin/lotto/LottoTest.kt +++ b/src/test/kotlin/lotto/LottoTest.kt @@ -1,5 +1,6 @@ package lotto +import lotto.domain.model.Lotto import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows From f8d88790d04d353d36b27b9b3683919ae7e6e6bd Mon Sep 17 00:00:00 2001 From: ErroredPasta Date: Sun, 5 Nov 2023 20:58:19 +0900 Subject: [PATCH 03/20] =?UTF-8?q?test:=20Money=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/lotto/domain/model/MoneyTest.kt | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/test/kotlin/lotto/domain/model/MoneyTest.kt diff --git a/src/test/kotlin/lotto/domain/model/MoneyTest.kt b/src/test/kotlin/lotto/domain/model/MoneyTest.kt new file mode 100644 index 000000000..4c5857c69 --- /dev/null +++ b/src/test/kotlin/lotto/domain/model/MoneyTest.kt @@ -0,0 +1,44 @@ +package lotto.domain.model + +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatIllegalArgumentException +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test + +class MoneyTest { + @Test + @DisplayName("Money 생성시 입력이 올바른 경우 성공적으로 Money 생성") + fun createMoney_validateAmount_createdSuccessfully() { + // when + val money = Money(amount = VALID_NUMBER) + + // then + assertThat(money.amount).isEqualTo(VALID_NUMBER) + } + + @Test + @DisplayName("Money 생성시 입력이 ${Money.DIVISION_AMOUNT}로 나누어 떨어지지 않을 경우 예외 발생") + fun createMoney_amountIsNotDividableByDivisionAmount_throwIllegalArgumentException() { + assertThatIllegalArgumentException() + // when + .isThrownBy { Money(amount = NOT_DIVIDABLE_NUMBER) } + // then + .withMessage(Money.MUST_BE_DIVIDABLE_BY_DIVISION_AMOUNT_MESSAGE) // then + } + + @Test + @DisplayName("Money 생성시 입력이 ${Money.MIN_MONEY_AMOUNT}보다 작은 경우 예외 발생") + fun createMoney_amountIsLessThanMinimum_throwIllegalArgumentException() { + assertThatIllegalArgumentException() + // when + .isThrownBy { Money(amount = LESS_THEN_MIN_MONEY_AMOUNT) } + // then + .withMessage(Money.MUST_GREATER_THAN_MIN_MONEY_MESSAGE) // then + } + + companion object { + const val VALID_NUMBER = Money.DIVISION_AMOUNT * 2 + const val NOT_DIVIDABLE_NUMBER = VALID_NUMBER + 100 + const val LESS_THEN_MIN_MONEY_AMOUNT = Money.MIN_MONEY_AMOUNT - 100 + } +} \ No newline at end of file From 76b8d877a796c8b531d4c510c6355e2cf2288b78 Mon Sep 17 00:00:00 2001 From: ErroredPasta Date: Wed, 8 Nov 2023 19:35:49 +0900 Subject: [PATCH 04/20] =?UTF-8?q?docs:=20README=EC=9D=98=20=EA=B8=B0?= =?UTF-8?q?=EB=8A=A5=20=EB=B6=80=EB=B6=84=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/README.md | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/docs/README.md b/docs/README.md index 33d89d999..ad82fc3b6 100644 --- a/docs/README.md +++ b/docs/README.md @@ -9,21 +9,20 @@ 1. **금액 입력** : 사용자가 로또를 구매에 사용할 금액을 입력합니다. - 금액이 1000원으로 나누어 떨어지도록 입력해야 합니다. - 금액으로 숫자를 입력해야 합니다. -2. **무작위 로또 생성**: (사용자가 입력한 금액 / 1000)만큼의 로또를 무작위 번호로 생성합니다. - - 생성할 때 정렬된 리스트로 생성 -3. **로또 번호 출력**: 무작위로 생성된 로또 번호들을 오름차순으로 출력합니다, -4. **당첨 번호 입력** : 사용자가 당첨 번호를 입력합니다. +2. **무작위 로또 생성** : (사용자가 입력한 금액 / 1000)만큼의 로또를 무작위 번호로 생성하여 오름차순으로 출력합니다. + - 생성할 때 정렬된 리스트로 생성합니다. +3. **당첨 번호 입력** : 사용자가 당첨 번호를 입력합니다. - 입력으로 숫자를 입력해야 합니다. - 숫자들은 ,로 구분됩니다. - 숫자를 6개 입력해야 합니다. - 숫자들은 1 ~ 45 사이의 값이어야 합니다. - 숫자들중 중복이 존재하지 않아야 합니다. -5. **보너스 번호 입력** : 사용자가 보너스 번호를 입력합니다. +4. **보너스 번호 입력** : 사용자가 보너스 번호를 입력합니다. - 입력으로 숫자를 입력해야 합니다. - 숫자가 1 ~ 45 사이의 값이어야 합니다. - 숫자가 당첨 번호와 중복이 되면 안됩니다. -6. **당첨 통계 출력** : 생성된 로또 번호와 사용자가 입력한 당첨 번호와 보너스 번호를 이용하여 당첨 통계를 계산하고 출력합니다. -7. **수익률 출력** : 사용자가 입력한 금액과 당첨 통계를 이용하여 수익률을 계산하여 출력합니다. +5. **당첨 통계 계산** : 생성된 로또 번호와 사용자가 입력한 당첨 번호와 보너스 번호를 이용하여 당첨 통계를 계산하고 출력합니다. +6. **수익률 계산** : 사용자가 입력한 금액과 당첨 통계를 이용하여 수익률을 계산하여 출력합니다. ## 사용 방법 From ce823fe0ebf47de09eb0919c77ca6eb878eb5b40 Mon Sep 17 00:00:00 2001 From: ErroredPasta Date: Wed, 8 Nov 2023 19:38:00 +0900 Subject: [PATCH 05/20] =?UTF-8?q?feat:=20(=EC=9E=85=EB=A0=A5=20=EB=B0=9B?= =?UTF-8?q?=EC=9D=80=20=EA=B8=88=EC=95=A1=20/=201000)=EB=A7=8C=ED=81=BC=20?= =?UTF-8?q?=EB=A1=9C=EB=98=90=EB=A5=BC=20=EC=83=9D=EC=84=B1=ED=95=98?= =?UTF-8?q?=EC=97=AC=20=EC=B6=9C=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/lotto/Application.kt | 5 +++- src/main/kotlin/lotto/LottoSimulator.kt | 1 + .../data/datasource/RandomNumberDataSource.kt | 5 ++++ .../data/repository/LottoRepositoryImpl.kt | 24 +++++++++++++++++ .../DuplicatedNumberExistException.kt | 3 +++ src/main/kotlin/lotto/domain/model/Lotto.kt | 27 +++++++++++++++++-- .../domain/repository/LottoRepository.kt | 7 +++++ .../lotto/ui/presenter/LottoPresenter.kt | 24 ++++++++++++++--- .../kotlin/lotto/ui/view/ConsoleLottoView.kt | 14 ++++++++++ src/main/kotlin/lotto/ui/view/LottoView.kt | 4 +++ 10 files changed, 108 insertions(+), 6 deletions(-) create mode 100644 src/main/kotlin/lotto/data/datasource/RandomNumberDataSource.kt create mode 100644 src/main/kotlin/lotto/data/repository/LottoRepositoryImpl.kt create mode 100644 src/main/kotlin/lotto/domain/exception/DuplicatedNumberExistException.kt create mode 100644 src/main/kotlin/lotto/domain/repository/LottoRepository.kt diff --git a/src/main/kotlin/lotto/Application.kt b/src/main/kotlin/lotto/Application.kt index a09e85ae8..77d0901b6 100644 --- a/src/main/kotlin/lotto/Application.kt +++ b/src/main/kotlin/lotto/Application.kt @@ -1,11 +1,14 @@ package lotto +import camp.nextstep.edu.missionutils.Randoms +import lotto.data.repository.LottoRepositoryImpl import lotto.ui.presenter.LottoPresenter import lotto.ui.view.ConsoleLottoView fun main() { val view = ConsoleLottoView() - val presenter = LottoPresenter(view = view) + val repository = LottoRepositoryImpl(randomNumberDataSource = Randoms::pickUniqueNumbersInRange) + val presenter = LottoPresenter(view = view, repository = repository) LottoSimulator(view = view, presenter = presenter).start() } diff --git a/src/main/kotlin/lotto/LottoSimulator.kt b/src/main/kotlin/lotto/LottoSimulator.kt index 992c9442f..87bad3548 100644 --- a/src/main/kotlin/lotto/LottoSimulator.kt +++ b/src/main/kotlin/lotto/LottoSimulator.kt @@ -11,5 +11,6 @@ class LottoSimulator( view.bindPresenter(presenter = presenter) view.onStart() + view.onGetMoneyDone() } } \ No newline at end of file diff --git a/src/main/kotlin/lotto/data/datasource/RandomNumberDataSource.kt b/src/main/kotlin/lotto/data/datasource/RandomNumberDataSource.kt new file mode 100644 index 000000000..03ec65d37 --- /dev/null +++ b/src/main/kotlin/lotto/data/datasource/RandomNumberDataSource.kt @@ -0,0 +1,5 @@ +package lotto.data.datasource + +fun interface RandomNumberDataSource { + fun pickUniqueNumbersInRange(startInclusive: Int, endInclusive: Int, count: Int): List +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/data/repository/LottoRepositoryImpl.kt b/src/main/kotlin/lotto/data/repository/LottoRepositoryImpl.kt new file mode 100644 index 000000000..fe5061c3e --- /dev/null +++ b/src/main/kotlin/lotto/data/repository/LottoRepositoryImpl.kt @@ -0,0 +1,24 @@ +package lotto.data.repository + +import lotto.data.datasource.RandomNumberDataSource +import lotto.domain.model.Lotto +import lotto.domain.repository.LottoRepository + +class LottoRepositoryImpl( + private val randomNumberDataSource: RandomNumberDataSource +) : LottoRepository { + override fun getLottoes(amount: Int): List { + val lottoes = mutableListOf() + repeat(amount) { + val numbers = randomNumberDataSource.pickUniqueNumbersInRange( + Lotto.MIN_NUMBER, + Lotto.MAX_NUMBER, + Lotto.NUMBER_OF_LOTTO_NUMBERS + ).sortedBy { it } + + lottoes.add(Lotto(numbers = numbers)) + } + + return lottoes + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/domain/exception/DuplicatedNumberExistException.kt b/src/main/kotlin/lotto/domain/exception/DuplicatedNumberExistException.kt new file mode 100644 index 000000000..b298a45a0 --- /dev/null +++ b/src/main/kotlin/lotto/domain/exception/DuplicatedNumberExistException.kt @@ -0,0 +1,3 @@ +package lotto.domain.exception + +class DuplicatedNumberExistException(message: String) : RuntimeException(message) \ No newline at end of file diff --git a/src/main/kotlin/lotto/domain/model/Lotto.kt b/src/main/kotlin/lotto/domain/model/Lotto.kt index 82fe0372f..a5267f35c 100644 --- a/src/main/kotlin/lotto/domain/model/Lotto.kt +++ b/src/main/kotlin/lotto/domain/model/Lotto.kt @@ -2,8 +2,31 @@ package lotto.domain.model class Lotto(private val numbers: List) { init { - require(numbers.size == 6) + require(numbers.size == NUMBER_OF_LOTTO_NUMBERS) { MISMATCH_NUMBER_OF_LOTTO_NUMBERS_MESSAGE } + require(!numbers.containsDuplicatedNumber()) { DUPLICATED_NUMBER_EXIST_MESSAGE } + require(numbers.inValidRange()) { NUMBERS_NOT_IN_VALID_RANGE } + require(numbers.inAscendingOrder()) { NUMBERS_NOT_IN_ASCENDING_ORDER } } - // TODO: 추가 기능 구현 + private fun List.containsDuplicatedNumber(): Boolean = this.toSet().size != this.size + + private fun List.inValidRange(): Boolean { + this.forEach { if (it !in validRange) return false } + return true + } + + private fun List.inAscendingOrder(): Boolean = this == this.sortedBy { it } + + override fun toString(): String = numbers.toString() + + companion object { + const val MIN_NUMBER = 1 + const val MAX_NUMBER = 45 + val validRange = (MIN_NUMBER..MAX_NUMBER) + const val NUMBER_OF_LOTTO_NUMBERS = 6 + const val MISMATCH_NUMBER_OF_LOTTO_NUMBERS_MESSAGE = "${NUMBER_OF_LOTTO_NUMBERS}의 숫자가 필요합니다." + const val DUPLICATED_NUMBER_EXIST_MESSAGE = "중복된 숫자가 존재합니다." + const val NUMBERS_NOT_IN_VALID_RANGE = "모든 숫자는 반드시 $MIN_NUMBER~$MAX_NUMBER 사이에 존재해야 합니다." + const val NUMBERS_NOT_IN_ASCENDING_ORDER = "로또 번호가 오름차순이 되도록 해주세요." + } } diff --git a/src/main/kotlin/lotto/domain/repository/LottoRepository.kt b/src/main/kotlin/lotto/domain/repository/LottoRepository.kt new file mode 100644 index 000000000..9da306901 --- /dev/null +++ b/src/main/kotlin/lotto/domain/repository/LottoRepository.kt @@ -0,0 +1,7 @@ +package lotto.domain.repository + +import lotto.domain.model.Lotto + +interface LottoRepository { + fun getLottoes(amount: Int): List +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt b/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt index 984ed2c9a..10abe507d 100644 --- a/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt +++ b/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt @@ -1,10 +1,12 @@ package lotto.ui.presenter import lotto.domain.model.Money +import lotto.domain.repository.LottoRepository import lotto.ui.view.LottoView class LottoPresenter( - private val view: LottoView + private val view: LottoView, + private val repository: LottoRepository ) { // inline class는 lateinit var 불가 private var _money: Money? = null @@ -17,10 +19,26 @@ class LottoPresenter( view.getMoney() }.onSuccess { _money = it + onSuccessGetMoney() }.onFailure { error -> view.displayErrorMessage(message = error.message) - view.displayEnterMoney() - view.getMoney() + onFailureGetMoney() } } + + private fun onSuccessGetMoney() { + view.displayNumberOfBoughtLottoes(number = money.amount / Money.DIVISION_AMOUNT) + } + + private fun onFailureGetMoney() { + view.displayEnterMoney() + view.getMoney() + } + + fun getLottoes() { + view.displayNumberOfBoughtLottoes(number = money.amount / Money.DIVISION_AMOUNT) + + val lottoes = repository.getLottoes(amount = money.amount / Money.DIVISION_AMOUNT) + view.displayLottoes(lottoes = lottoes) + } } \ No newline at end of file diff --git a/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt index 8a2163b97..f19679e7d 100644 --- a/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt +++ b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt @@ -3,6 +3,7 @@ package lotto.ui.view import camp.nextstep.edu.missionutils.Console import lotto.domain.model.Money import lotto.ui.presenter.LottoPresenter +import lotto.domain.model.Lotto class ConsoleLottoView : LottoView { private lateinit var presenter: LottoPresenter @@ -21,6 +22,18 @@ class ConsoleLottoView : LottoView { override fun getMoney(): Money = Money(amount = Console.readLine().toInt()) + override fun onGetMoneyDone() { + presenter.getLottoes() + } + + override fun displayNumberOfBoughtLottoes(number: Int) { + println("${number}$NUMBER_OF_BOUGHT_LOTTOES_MESSAGE") + } + + override fun displayLottoes(lottoes: List) { + lottoes.forEach(::println) + } + override fun displayErrorMessage(message: String?) { println("$ERROR_MESSAGE_PREFIX ${message ?: DEFAULT_ERROR_MESSAGE}") } @@ -29,5 +42,6 @@ class ConsoleLottoView : LottoView { const val ENTER_MONEY_MESSAGE = "구입금액을 입력해 주세요." const val ERROR_MESSAGE_PREFIX = "[ERROR]" const val DEFAULT_ERROR_MESSAGE = "에러가 발생했습니다. 다시 시도해주세요." + const val NUMBER_OF_BOUGHT_LOTTOES_MESSAGE = "개를 구매했습니다." } } \ No newline at end of file diff --git a/src/main/kotlin/lotto/ui/view/LottoView.kt b/src/main/kotlin/lotto/ui/view/LottoView.kt index 6ca63aefc..46227f5d1 100644 --- a/src/main/kotlin/lotto/ui/view/LottoView.kt +++ b/src/main/kotlin/lotto/ui/view/LottoView.kt @@ -1,5 +1,6 @@ package lotto.ui.view +import lotto.domain.model.Lotto import lotto.domain.model.Money import lotto.ui.presenter.LottoPresenter @@ -8,5 +9,8 @@ interface LottoView { fun onStart() fun displayEnterMoney() fun getMoney(): Money + fun onGetMoneyDone() + fun displayNumberOfBoughtLottoes(number: Int) + fun displayLottoes(lottoes: List) fun displayErrorMessage(message: String?) } \ No newline at end of file From 150f756cc11911f87f95c2bbf6a0397fde75f48e Mon Sep 17 00:00:00 2001 From: ErroredPasta Date: Wed, 8 Nov 2023 19:38:53 +0900 Subject: [PATCH 06/20] =?UTF-8?q?refactor:=20UI=EC=9D=98=20View=EC=99=80?= =?UTF-8?q?=20Presenter=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lotto/ui/presenter/LottoPresenter.kt | 27 +++++++++---------- .../kotlin/lotto/ui/view/ConsoleLottoView.kt | 27 ++++++------------- src/main/kotlin/lotto/ui/view/LottoView.kt | 7 ++--- 3 files changed, 23 insertions(+), 38 deletions(-) diff --git a/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt b/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt index 10abe507d..db2d37219 100644 --- a/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt +++ b/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt @@ -1,5 +1,6 @@ package lotto.ui.presenter +import lotto.domain.model.Lotto import lotto.domain.model.Money import lotto.domain.repository.LottoRepository import lotto.ui.view.LottoView @@ -12,33 +13,31 @@ class LottoPresenter( private var _money: Money? = null private val money get() = requireNotNull(_money) + private lateinit var lottoes: List + fun getMoney() { - view.displayEnterMoney() + view.displayMessage(ENTER_MONEY_MESSAGE) runCatching { view.getMoney() }.onSuccess { _money = it - onSuccessGetMoney() }.onFailure { error -> view.displayErrorMessage(message = error.message) - onFailureGetMoney() + getMoney() } } - private fun onSuccessGetMoney() { - view.displayNumberOfBoughtLottoes(number = money.amount / Money.DIVISION_AMOUNT) - } + fun getLottoes() { + val numberOfBoughtLottoes = money.amount / Money.DIVISION_AMOUNT + view.displayMessage("\n$numberOfBoughtLottoes$NUMBER_OF_BOUGHT_LOTTOES_MESSAGE") - private fun onFailureGetMoney() { - view.displayEnterMoney() - view.getMoney() + lottoes = repository.getLottoes(amount = money.amount / Money.DIVISION_AMOUNT) + lottoes.forEach { view.displayMessage(it.toString()) } } - fun getLottoes() { - view.displayNumberOfBoughtLottoes(number = money.amount / Money.DIVISION_AMOUNT) - - val lottoes = repository.getLottoes(amount = money.amount / Money.DIVISION_AMOUNT) - view.displayLottoes(lottoes = lottoes) + companion object { + const val ENTER_MONEY_MESSAGE = "구입금액을 입력해 주세요." + const val NUMBER_OF_BOUGHT_LOTTOES_MESSAGE = "개를 구매했습니다." } } \ No newline at end of file diff --git a/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt index f19679e7d..00f2eb81c 100644 --- a/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt +++ b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt @@ -3,7 +3,6 @@ package lotto.ui.view import camp.nextstep.edu.missionutils.Console import lotto.domain.model.Money import lotto.ui.presenter.LottoPresenter -import lotto.domain.model.Lotto class ConsoleLottoView : LottoView { private lateinit var presenter: LottoPresenter @@ -12,12 +11,16 @@ class ConsoleLottoView : LottoView { this.presenter = presenter } - override fun onStart() { - presenter.getMoney() + override fun displayMessage(message: String) { + println(message) + } + + override fun displayErrorMessage(message: String?) { + println("$ERROR_MESSAGE_PREFIX ${message ?: DEFAULT_ERROR_MESSAGE}") } - override fun displayEnterMoney() { - println(ENTER_MONEY_MESSAGE) + override fun onStart() { + presenter.getMoney() } override fun getMoney(): Money = Money(amount = Console.readLine().toInt()) @@ -26,22 +29,8 @@ class ConsoleLottoView : LottoView { presenter.getLottoes() } - override fun displayNumberOfBoughtLottoes(number: Int) { - println("${number}$NUMBER_OF_BOUGHT_LOTTOES_MESSAGE") - } - - override fun displayLottoes(lottoes: List) { - lottoes.forEach(::println) - } - - override fun displayErrorMessage(message: String?) { - println("$ERROR_MESSAGE_PREFIX ${message ?: DEFAULT_ERROR_MESSAGE}") - } - companion object { - const val ENTER_MONEY_MESSAGE = "구입금액을 입력해 주세요." const val ERROR_MESSAGE_PREFIX = "[ERROR]" const val DEFAULT_ERROR_MESSAGE = "에러가 발생했습니다. 다시 시도해주세요." - const val NUMBER_OF_BOUGHT_LOTTOES_MESSAGE = "개를 구매했습니다." } } \ No newline at end of file diff --git a/src/main/kotlin/lotto/ui/view/LottoView.kt b/src/main/kotlin/lotto/ui/view/LottoView.kt index 46227f5d1..af6da454a 100644 --- a/src/main/kotlin/lotto/ui/view/LottoView.kt +++ b/src/main/kotlin/lotto/ui/view/LottoView.kt @@ -1,16 +1,13 @@ package lotto.ui.view -import lotto.domain.model.Lotto import lotto.domain.model.Money import lotto.ui.presenter.LottoPresenter interface LottoView { fun bindPresenter(presenter: LottoPresenter) + fun displayMessage(message: String) + fun displayErrorMessage(message: String?) fun onStart() - fun displayEnterMoney() fun getMoney(): Money fun onGetMoneyDone() - fun displayNumberOfBoughtLottoes(number: Int) - fun displayLottoes(lottoes: List) - fun displayErrorMessage(message: String?) } \ No newline at end of file From 58c2884cf398410af1f1e59f3135558db33a8a61 Mon Sep 17 00:00:00 2001 From: ErroredPasta Date: Mon, 6 Nov 2023 00:17:48 +0900 Subject: [PATCH 07/20] =?UTF-8?q?test:=20LottoRepository=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 갯수가 주어질 경우 해당 갯수만큼 로또를 생성하는지 - 로또의 번호가 오름차순이 되도록 생성되는지 --- .../repository/LottoRepositoryImplTest.kt | 63 +++++++++++++++++++ 1 file changed, 63 insertions(+) create mode 100644 src/test/kotlin/lotto/data/repository/LottoRepositoryImplTest.kt diff --git a/src/test/kotlin/lotto/data/repository/LottoRepositoryImplTest.kt b/src/test/kotlin/lotto/data/repository/LottoRepositoryImplTest.kt new file mode 100644 index 000000000..550f4b377 --- /dev/null +++ b/src/test/kotlin/lotto/data/repository/LottoRepositoryImplTest.kt @@ -0,0 +1,63 @@ +package lotto.data.repository + +import camp.nextstep.edu.missionutils.Randoms +import lotto.data.datasource.RandomNumberDataSource +import lotto.domain.repository.LottoRepository +import org.assertj.core.api.Assertions.assertThat +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import org.junit.jupiter.params.provider.ValueSource +import java.util.stream.Stream + +class LottoRepositoryImplTest { + private lateinit var repository: LottoRepository + private lateinit var dataSource: RandomNumberDataSource + + @ParameterizedTest + @ValueSource(ints = [1, 2, 3, 4, 5]) + @DisplayName("로또를 구매할 갯수가 주어질 경우 해당 갯수만큼 로또를 가져옴") + fun getLottoes_amountOfLottoesProvided_returnExactNumberOfLottoes(amount: Int) { + // given + dataSource = RandomNumberDataSource { startInclusive, endInclusive, count -> + Randoms.pickUniqueNumbersInRange(startInclusive, endInclusive, count) + } + + repository = LottoRepositoryImpl(randomNumberDataSource = dataSource) + + // when + val lottoes = repository.getLottoes(amount = amount) + + // then + assertThat(lottoes.size).isEqualTo(amount) + } + + @ParameterizedTest + @MethodSource("provideNumbersForLottoes") + @DisplayName("로또 생성시 번호가 오름차순이어야 함") + fun getLottoes_printLottoes_numbersShouldBeSortedInAscendingOrder(numbers: List) { + // given + dataSource = RandomNumberDataSource { _, _, _ -> numbers } + repository = LottoRepositoryImpl(randomNumberDataSource = dataSource) + + // when + val lottoes = repository.getLottoes(amount = DEFAULT_LOTTO_AMOUNT) + + // then + val expected = numbers.sortedBy { it }.toString() + lottoes.forEach { assertThat(it.toString()).isEqualTo(expected) } + } + + + companion object { + private const val DEFAULT_LOTTO_AMOUNT = 5 + + @JvmStatic + private fun provideNumbersForLottoes(): Stream = Stream.of( + Arguments.of(listOf(6, 5, 4, 3, 2, 1)), + Arguments.of(listOf(1, 3, 5, 7, 9, 11)), + Arguments.of(listOf(25, 42, 34, 12, 30, 7)), + ) + } +} \ No newline at end of file From ad0073e568f0e16b02541a572e6ea8d83408667c Mon Sep 17 00:00:00 2001 From: ErroredPasta Date: Mon, 6 Nov 2023 00:18:57 +0900 Subject: [PATCH 08/20] =?UTF-8?q?fix:=20=EB=A1=9C=EB=98=90=20=EB=B2=88?= =?UTF-8?q?=ED=98=B8=EA=B0=80=206=EA=B0=9C=20=EC=95=84=EB=8B=90=20?= =?UTF-8?q?=EA=B2=BD=EC=9A=B0=EC=9D=98=20=EC=98=88=EC=99=B8=20=EB=A9=94?= =?UTF-8?q?=EC=8B=9C=EC=A7=80=20=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/lotto/domain/model/Lotto.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/lotto/domain/model/Lotto.kt b/src/main/kotlin/lotto/domain/model/Lotto.kt index a5267f35c..305b4bcfa 100644 --- a/src/main/kotlin/lotto/domain/model/Lotto.kt +++ b/src/main/kotlin/lotto/domain/model/Lotto.kt @@ -24,7 +24,7 @@ class Lotto(private val numbers: List) { const val MAX_NUMBER = 45 val validRange = (MIN_NUMBER..MAX_NUMBER) const val NUMBER_OF_LOTTO_NUMBERS = 6 - const val MISMATCH_NUMBER_OF_LOTTO_NUMBERS_MESSAGE = "${NUMBER_OF_LOTTO_NUMBERS}의 숫자가 필요합니다." + const val MISMATCH_NUMBER_OF_LOTTO_NUMBERS_MESSAGE = "${NUMBER_OF_LOTTO_NUMBERS}개의 숫자가 필요합니다." const val DUPLICATED_NUMBER_EXIST_MESSAGE = "중복된 숫자가 존재합니다." const val NUMBERS_NOT_IN_VALID_RANGE = "모든 숫자는 반드시 $MIN_NUMBER~$MAX_NUMBER 사이에 존재해야 합니다." const val NUMBERS_NOT_IN_ASCENDING_ORDER = "로또 번호가 오름차순이 되도록 해주세요." From 79e89d34f8b185269f548c192b3a5fa72e731b96 Mon Sep 17 00:00:00 2001 From: ErroredPasta Date: Mon, 6 Nov 2023 01:14:45 +0900 Subject: [PATCH 09/20] =?UTF-8?q?feat:=20=EB=8B=B9=EC=B2=A8=20=EB=B2=88?= =?UTF-8?q?=ED=98=B8=EB=A5=BC=20=EC=9E=85=EB=A0=A5=20=EB=B0=9B=EB=8A=94=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/lotto/LottoSimulator.kt | 1 + .../lotto/domain/model/WinningNumbers.kt | 23 +++++++++++++++++++ .../lotto/ui/presenter/LottoPresenter.kt | 18 +++++++++++++++ .../kotlin/lotto/ui/view/ConsoleLottoView.kt | 11 +++++++++ src/main/kotlin/lotto/ui/view/LottoView.kt | 3 +++ 5 files changed, 56 insertions(+) create mode 100644 src/main/kotlin/lotto/domain/model/WinningNumbers.kt diff --git a/src/main/kotlin/lotto/LottoSimulator.kt b/src/main/kotlin/lotto/LottoSimulator.kt index 87bad3548..f79c43bca 100644 --- a/src/main/kotlin/lotto/LottoSimulator.kt +++ b/src/main/kotlin/lotto/LottoSimulator.kt @@ -12,5 +12,6 @@ class LottoSimulator( view.onStart() view.onGetMoneyDone() + view.onGetLottoesDone() } } \ No newline at end of file diff --git a/src/main/kotlin/lotto/domain/model/WinningNumbers.kt b/src/main/kotlin/lotto/domain/model/WinningNumbers.kt new file mode 100644 index 000000000..940d44cac --- /dev/null +++ b/src/main/kotlin/lotto/domain/model/WinningNumbers.kt @@ -0,0 +1,23 @@ +package lotto.domain.model + +@JvmInline +value class WinningNumbers(val numbers: List) { + init { + require(numbers.size == NUMBER_OF_WINNING_NUMBERS) { MISMATCH_NUMBER_OF_WINNING_NUMBERS } + require(!numbers.containsDuplicatedNumber()) { DUPLICATED_NUMBER_EXIST_EXCEPTION_MESSAGE } + require(numbers.inValidRange()) { Lotto.NUMBERS_NOT_IN_VALID_RANGE } + } + + private fun List.containsDuplicatedNumber(): Boolean = this.toSet().size != this.size + + private fun List.inValidRange(): Boolean { + this.forEach { if (it !in Lotto.validRange) return false } + return true + } + + companion object { + const val NUMBER_OF_WINNING_NUMBERS = Lotto.NUMBER_OF_LOTTO_NUMBERS + const val MISMATCH_NUMBER_OF_WINNING_NUMBERS = "${NUMBER_OF_WINNING_NUMBERS}개의 숫자가 필요합니다." + const val DUPLICATED_NUMBER_EXIST_EXCEPTION_MESSAGE = "중복된 숫자가 존재합니다." + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt b/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt index db2d37219..0face9ef2 100644 --- a/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt +++ b/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt @@ -2,6 +2,7 @@ package lotto.ui.presenter import lotto.domain.model.Lotto import lotto.domain.model.Money +import lotto.domain.model.WinningNumbers import lotto.domain.repository.LottoRepository import lotto.ui.view.LottoView @@ -15,6 +16,9 @@ class LottoPresenter( private lateinit var lottoes: List + private var _winningNumbers: WinningNumbers? = null + private val winningNumbers get() = requireNotNull(_winningNumbers) + fun getMoney() { view.displayMessage(ENTER_MONEY_MESSAGE) @@ -36,8 +40,22 @@ class LottoPresenter( lottoes.forEach { view.displayMessage(it.toString()) } } + fun getWinningNumbers() { + view.displayMessage("\n$ENTER_WINNING_NUMBERS_MESSAGE") + + runCatching { + view.getWinningNumbers() + }.onSuccess { + _winningNumbers = it + }.onFailure { error -> + view.displayErrorMessage(message = error.message) + getWinningNumbers() + } + } + companion object { const val ENTER_MONEY_MESSAGE = "구입금액을 입력해 주세요." const val NUMBER_OF_BOUGHT_LOTTOES_MESSAGE = "개를 구매했습니다." + const val ENTER_WINNING_NUMBERS_MESSAGE = "당첨 번호를 입력해 주세요." } } \ No newline at end of file diff --git a/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt index 00f2eb81c..c14f895f4 100644 --- a/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt +++ b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt @@ -2,6 +2,7 @@ package lotto.ui.view import camp.nextstep.edu.missionutils.Console import lotto.domain.model.Money +import lotto.domain.model.WinningNumbers import lotto.ui.presenter.LottoPresenter class ConsoleLottoView : LottoView { @@ -29,8 +30,18 @@ class ConsoleLottoView : LottoView { presenter.getLottoes() } + override fun onGetLottoesDone() { + presenter.getWinningNumbers() + } + + override fun getWinningNumbers(): WinningNumbers { + val numbers = Console.readLine().split(NUMBER_DELIMITER).map(String::toInt) + return WinningNumbers(numbers = numbers) + } + companion object { const val ERROR_MESSAGE_PREFIX = "[ERROR]" const val DEFAULT_ERROR_MESSAGE = "에러가 발생했습니다. 다시 시도해주세요." + const val NUMBER_DELIMITER = ',' } } \ No newline at end of file diff --git a/src/main/kotlin/lotto/ui/view/LottoView.kt b/src/main/kotlin/lotto/ui/view/LottoView.kt index af6da454a..2760af0ed 100644 --- a/src/main/kotlin/lotto/ui/view/LottoView.kt +++ b/src/main/kotlin/lotto/ui/view/LottoView.kt @@ -1,6 +1,7 @@ package lotto.ui.view import lotto.domain.model.Money +import lotto.domain.model.WinningNumbers import lotto.ui.presenter.LottoPresenter interface LottoView { @@ -10,4 +11,6 @@ interface LottoView { fun onStart() fun getMoney(): Money fun onGetMoneyDone() + fun onGetLottoesDone() + fun getWinningNumbers(): WinningNumbers } \ No newline at end of file From 8fe08010dc2ea74f58ba755d093e28689034544c Mon Sep 17 00:00:00 2001 From: ErroredPasta Date: Wed, 8 Nov 2023 21:09:13 +0900 Subject: [PATCH 10/20] =?UTF-8?q?test:=20WinningNumbers=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../lotto/domain/model/WinningNumbersTest.kt | 87 +++++++++++++++++++ 1 file changed, 87 insertions(+) create mode 100644 src/test/kotlin/lotto/domain/model/WinningNumbersTest.kt diff --git a/src/test/kotlin/lotto/domain/model/WinningNumbersTest.kt b/src/test/kotlin/lotto/domain/model/WinningNumbersTest.kt new file mode 100644 index 000000000..1f805fe14 --- /dev/null +++ b/src/test/kotlin/lotto/domain/model/WinningNumbersTest.kt @@ -0,0 +1,87 @@ +package lotto.domain.model + +import org.assertj.core.api.Assertions.assertThat +import org.assertj.core.api.Assertions.assertThatIllegalArgumentException +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream + +class WinningNumbersTest { + @ParameterizedTest + @MethodSource("provideValidNumbers") + @DisplayName("WinningNumbers 생성시 올바른 숫자들을 입력할 경우 성공적으로 생성됨") + fun createWinningNumbers_validNumbers_createdSuccessfully(numbers: List) { + // when + val winningNumbers = WinningNumbers(numbers = numbers) + + // then + assertThat(winningNumbers.numbers).isEqualTo(numbers) + } + + @ParameterizedTest + @MethodSource("provideValidNumbers") + @DisplayName("WinningNumbers 생성시 올바른 숫자들을 입력할 경우 numbers의 크기는 항상 ${Lotto.NUMBER_OF_LOTTO_NUMBERS}") + fun createWinningNumbers_validNumbers_sizeIsEqualToNumberOfLottoNumbers(numbers: List) { + // when + val winningNumbers = WinningNumbers(numbers = numbers) + + // then + assertThat(winningNumbers.numbers.size).isEqualTo(Lotto.NUMBER_OF_LOTTO_NUMBERS) + } + + @Test + @DisplayName("WinningNumbers 생성시 ${Lotto.MIN_NUMBER}~${Lotto.MAX_NUMBER} 사이의 숫자가 아닌 것이 있으면 예외 발생") + fun createWinningNumbers_numbersOutOfRange_throwIllegalArgumentException() { + // given + val outOfRangeNumbers = listOf(1, 2, 3, 4, 5, 46) + + assertThatIllegalArgumentException() + .isThrownBy { WinningNumbers(outOfRangeNumbers) } // when + .withMessage(Lotto.NUMBERS_NOT_IN_VALID_RANGE) // then + } + + @Test + @DisplayName("Winning Numbers 생성시 중복된 숫자가 존재할 경우 예외 발생") + fun createWinningNumbers_duplicatedNumbersExist_throwIllegalArgumentException() { + // given + val duplicatedNumbers = listOf(1, 2, 3, 4, 5, 1) + + assertThatIllegalArgumentException() + .isThrownBy { WinningNumbers(duplicatedNumbers) } // when + .withMessage(WinningNumbers.DUPLICATED_NUMBER_EXIST_EXCEPTION_MESSAGE) // then + + } + + @ParameterizedTest + @MethodSource("provideNumbersSizeIsNotEqualToNumberOfLottoNumbers") + @DisplayName("Winning Numbers 생성시 숫자가 ${Lotto.NUMBER_OF_LOTTO_NUMBERS}개가 아닌 경우 예외 발생") + fun createWinningNumbers_numbersSizeIsNotEqualToNumberOfLottoNumbers_throwIllegalArgumentException(numbers: List) { + assertThatIllegalArgumentException() + .isThrownBy { WinningNumbers(numbers) } // when + .withMessage(WinningNumbers.MISMATCH_NUMBER_OF_WINNING_NUMBERS) // then + } + + + companion object { + @JvmStatic + private fun provideValidNumbers(): Stream = Stream.of( + Arguments.of(listOf(1, 2, 3, 4, 5, 6)), + Arguments.of(listOf(10, 34, 23, 12, 11, 9)), + ) + + @JvmStatic + private fun provideNumbersSizeIsNotEqualToNumberOfLottoNumbers(): Stream = Stream.of( + Arguments.of(listOf(1)), + Arguments.of(listOf(1, 2)), + Arguments.of(listOf(1, 2, 3)), + Arguments.of(listOf(1, 2, 3, 4)), + Arguments.of(listOf(1, 2, 3, 4, 5)), + Arguments.of(listOf(1, 2, 3, 4, 5, 6, 7)), + Arguments.of(listOf(1, 2, 3, 4, 5, 6, 7, 8)), + Arguments.of(listOf(1, 2, 3, 4, 5, 6, 7, 8, 9)), + ) + } +} \ No newline at end of file From 08c4c44fd02b7da2415c0400db23dcd138f9e826 Mon Sep 17 00:00:00 2001 From: ErroredPasta Date: Wed, 8 Nov 2023 21:29:31 +0900 Subject: [PATCH 11/20] =?UTF-8?q?test:=20Lotto=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/kotlin/lotto/LottoTest.kt | 55 +++++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/src/test/kotlin/lotto/LottoTest.kt b/src/test/kotlin/lotto/LottoTest.kt index a629a460d..8e2fbe24b 100644 --- a/src/test/kotlin/lotto/LottoTest.kt +++ b/src/test/kotlin/lotto/LottoTest.kt @@ -1,8 +1,14 @@ package lotto import lotto.domain.model.Lotto +import org.assertj.core.api.Assertions.assertThatIllegalArgumentException +import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test import org.junit.jupiter.api.assertThrows +import org.junit.jupiter.params.ParameterizedTest +import org.junit.jupiter.params.provider.Arguments +import org.junit.jupiter.params.provider.MethodSource +import java.util.stream.Stream class LottoTest { @@ -13,7 +19,6 @@ class LottoTest { } } - // TODO: 이 테스트가 통과할 수 있게 구현 코드 작성 @Test fun `로또 번호에 중복된 숫자가 있으면 예외가 발생한다`() { assertThrows { @@ -22,4 +27,52 @@ class LottoTest { } // 아래에 추가 테스트 작성 가능 + @ParameterizedTest + @MethodSource("provideNumbersSizeIsSmallerThanNumberOfLottoNumbers") + @DisplayName("Lotto 생성시 숫자가 ${Lotto.NUMBER_OF_LOTTO_NUMBERS}개보다 적을 경우 예외 발생") + fun createLotto_numbersSizeIsSmallerThanNumberOfLottoNumbers_throwIllegalArgumentException(numbers: List) { + assertThatIllegalArgumentException() + .isThrownBy { Lotto(numbers) } // when + .withMessage(Lotto.MISMATCH_NUMBER_OF_LOTTO_NUMBERS_MESSAGE) // then + } + + @Test + @DisplayName("Lotto 생성시 ${Lotto.MIN_NUMBER}~${Lotto.MAX_NUMBER} 사이의 숫자가 아닌 것이 있으면 예외 발생") + fun createLotto_numbersOutOfRange_throwIllegalArgumentException() { + // given + val numbersOutOfRange = listOf(1, 2, 3, 4, 5, 46) + + assertThatIllegalArgumentException() + .isThrownBy { Lotto(numbersOutOfRange) } // when + .withMessage(Lotto.NUMBERS_NOT_IN_VALID_RANGE) // then + } + + @ParameterizedTest + @MethodSource("provideNumbersNotInAscendingOrder") + @DisplayName("Lotto 생성시 숫자가 오름차순이 아닌 경우 예외 발생") + fun createLotto_numbersNotInAscendingOrder_throwIllegalArgumentException(numbers: List) { + assertThatIllegalArgumentException() + .isThrownBy { Lotto(numbers) } // when + .withMessage(Lotto.NUMBERS_NOT_IN_ASCENDING_ORDER) // then + } + + companion object { + @JvmStatic + private fun provideNumbersSizeIsSmallerThanNumberOfLottoNumbers(): Stream = Stream.of( + Arguments.of(listOf(1)), + Arguments.of(listOf(1, 2)), + Arguments.of(listOf(1, 2, 3)), + Arguments.of(listOf(1, 2, 3, 4)), + Arguments.of(listOf(1, 2, 3, 4, 5)), + ) + + @JvmStatic + private fun provideNumbersNotInAscendingOrder(): Stream = Stream.of( + Arguments.of(listOf(1, 2, 3, 4, 6, 5)), + Arguments.of(listOf(2, 1, 3, 4, 6, 5)), + Arguments.of(listOf(4, 5, 6, 1, 2, 3)), + Arguments.of(listOf(1, 2, 4, 3, 5, 6)), + Arguments.of(listOf(6, 5, 4, 3, 2, 1)), + ) + } } From 6ffa698b63378160f0ab98065f1230250fcdbddd Mon Sep 17 00:00:00 2001 From: ErroredPasta Date: Wed, 8 Nov 2023 21:42:43 +0900 Subject: [PATCH 12/20] =?UTF-8?q?feat:=20=EB=B3=B4=EB=84=88=EC=8A=A4=20?= =?UTF-8?q?=EC=88=AB=EC=9E=90=EB=A5=BC=20=EC=9E=85=EB=A0=A5=EB=B0=9B?= =?UTF-8?q?=EB=8A=94=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/lotto/LottoSimulator.kt | 1 + .../kotlin/lotto/domain/model/BonusNumber.kt | 13 ++++++++++++ .../lotto/domain/model/WinningNumbers.kt | 2 ++ .../lotto/ui/presenter/LottoPresenter.kt | 20 +++++++++++++++++++ .../kotlin/lotto/ui/view/ConsoleLottoView.kt | 11 +++++++++- src/main/kotlin/lotto/ui/view/LottoView.kt | 3 +++ 6 files changed, 49 insertions(+), 1 deletion(-) create mode 100644 src/main/kotlin/lotto/domain/model/BonusNumber.kt diff --git a/src/main/kotlin/lotto/LottoSimulator.kt b/src/main/kotlin/lotto/LottoSimulator.kt index f79c43bca..f3c85218d 100644 --- a/src/main/kotlin/lotto/LottoSimulator.kt +++ b/src/main/kotlin/lotto/LottoSimulator.kt @@ -13,5 +13,6 @@ class LottoSimulator( view.onStart() view.onGetMoneyDone() view.onGetLottoesDone() + view.onGetWinningNumbersDone() } } \ No newline at end of file diff --git a/src/main/kotlin/lotto/domain/model/BonusNumber.kt b/src/main/kotlin/lotto/domain/model/BonusNumber.kt new file mode 100644 index 000000000..a251fd8fc --- /dev/null +++ b/src/main/kotlin/lotto/domain/model/BonusNumber.kt @@ -0,0 +1,13 @@ +package lotto.domain.model + +@JvmInline +value class BonusNumber(val number: Int) { + init { + require(number in Lotto.validRange) { NUMBER_NOT_IN_VALID_RANGE_MESSAGE } + } + + companion object { + const val NUMBER_NOT_IN_VALID_RANGE_MESSAGE = "숫자가 $${Lotto.MIN_NUMBER}~${Lotto.MAX_NUMBER} 사이에 존재해야 합니다." + const val NUMBER_EXISTS_IN_WINNING_NUMBERS = "이미 숫자가 당첨 번호에 존재합니다." + } +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/domain/model/WinningNumbers.kt b/src/main/kotlin/lotto/domain/model/WinningNumbers.kt index 940d44cac..10bb660e3 100644 --- a/src/main/kotlin/lotto/domain/model/WinningNumbers.kt +++ b/src/main/kotlin/lotto/domain/model/WinningNumbers.kt @@ -15,6 +15,8 @@ value class WinningNumbers(val numbers: List) { return true } + operator fun contains(number: Int): Boolean = numbers.contains(number) + companion object { const val NUMBER_OF_WINNING_NUMBERS = Lotto.NUMBER_OF_LOTTO_NUMBERS const val MISMATCH_NUMBER_OF_WINNING_NUMBERS = "${NUMBER_OF_WINNING_NUMBERS}개의 숫자가 필요합니다." diff --git a/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt b/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt index 0face9ef2..027e8a541 100644 --- a/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt +++ b/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt @@ -1,5 +1,6 @@ package lotto.ui.presenter +import lotto.domain.model.BonusNumber import lotto.domain.model.Lotto import lotto.domain.model.Money import lotto.domain.model.WinningNumbers @@ -19,6 +20,9 @@ class LottoPresenter( private var _winningNumbers: WinningNumbers? = null private val winningNumbers get() = requireNotNull(_winningNumbers) + private var _bonusNumber: BonusNumber? = null + private val bonusNumber get() = requireNotNull(_bonusNumber) + fun getMoney() { view.displayMessage(ENTER_MONEY_MESSAGE) @@ -53,9 +57,25 @@ class LottoPresenter( } } + fun getBonusNumber() { + view.displayMessage("\n$ENTER_BONUS_NUMBER_MESSAGE") + + runCatching { + view.getBonusNumber().also { + require(it.number !in winningNumbers) { BonusNumber.NUMBER_EXISTS_IN_WINNING_NUMBERS } + } + }.onSuccess { + _bonusNumber = it + }.onFailure { error -> + view.displayErrorMessage(message = error.message) + getBonusNumber() + } + } + companion object { const val ENTER_MONEY_MESSAGE = "구입금액을 입력해 주세요." const val NUMBER_OF_BOUGHT_LOTTOES_MESSAGE = "개를 구매했습니다." const val ENTER_WINNING_NUMBERS_MESSAGE = "당첨 번호를 입력해 주세요." + const val ENTER_BONUS_NUMBER_MESSAGE = "보너스 번호를 입력해 주세요." } } \ No newline at end of file diff --git a/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt index c14f895f4..0149205fd 100644 --- a/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt +++ b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt @@ -1,6 +1,7 @@ package lotto.ui.view import camp.nextstep.edu.missionutils.Console +import lotto.domain.model.BonusNumber import lotto.domain.model.Money import lotto.domain.model.WinningNumbers import lotto.ui.presenter.LottoPresenter @@ -24,7 +25,7 @@ class ConsoleLottoView : LottoView { presenter.getMoney() } - override fun getMoney(): Money = Money(amount = Console.readLine().toInt()) + override fun getMoney(): Money = Money(amount = readInt()) override fun onGetMoneyDone() { presenter.getLottoes() @@ -39,6 +40,14 @@ class ConsoleLottoView : LottoView { return WinningNumbers(numbers = numbers) } + override fun onGetWinningNumbersDone() { + presenter.getBonusNumber() + } + + override fun getBonusNumber(): BonusNumber = BonusNumber(number = readInt()) + + private fun readInt(): Int = Console.readLine().toInt() + companion object { const val ERROR_MESSAGE_PREFIX = "[ERROR]" const val DEFAULT_ERROR_MESSAGE = "에러가 발생했습니다. 다시 시도해주세요." diff --git a/src/main/kotlin/lotto/ui/view/LottoView.kt b/src/main/kotlin/lotto/ui/view/LottoView.kt index 2760af0ed..39bc13b56 100644 --- a/src/main/kotlin/lotto/ui/view/LottoView.kt +++ b/src/main/kotlin/lotto/ui/view/LottoView.kt @@ -1,5 +1,6 @@ package lotto.ui.view +import lotto.domain.model.BonusNumber import lotto.domain.model.Money import lotto.domain.model.WinningNumbers import lotto.ui.presenter.LottoPresenter @@ -13,4 +14,6 @@ interface LottoView { fun onGetMoneyDone() fun onGetLottoesDone() fun getWinningNumbers(): WinningNumbers + fun onGetWinningNumbersDone() + fun getBonusNumber(): BonusNumber } \ No newline at end of file From d964b63d677449be3079ee6de7aa3fbe13d7deee Mon Sep 17 00:00:00 2001 From: ErroredPasta Date: Wed, 8 Nov 2023 21:47:24 +0900 Subject: [PATCH 13/20] =?UTF-8?q?fix:=20=EC=88=AB=EC=9E=90=EB=A5=BC=20?= =?UTF-8?q?=EC=9E=85=EB=A0=A5=ED=95=B4=EC=95=BC=ED=95=A0=20=EB=95=8C=20?= =?UTF-8?q?=EC=88=AB=EC=9E=90=EB=A5=BC=20=EC=9E=85=EB=A0=A5=20=ED=95=98?= =?UTF-8?q?=EC=A7=80=20=EC=95=8A=EC=9D=84=20=EA=B2=BD=EC=9A=B0=20=EC=98=AC?= =?UTF-8?q?=EB=B0=94=EB=A5=B8=20=EC=98=88=EC=99=B8=20=EB=A9=94=EC=8B=9C?= =?UTF-8?q?=EC=A7=80=EA=B0=80=20=EB=9C=A8=EB=8F=84=EB=A1=9D=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt | 14 ++++++++++++-- 1 file changed, 12 insertions(+), 2 deletions(-) diff --git a/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt index 0149205fd..69eecdf53 100644 --- a/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt +++ b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt @@ -36,7 +36,12 @@ class ConsoleLottoView : LottoView { } override fun getWinningNumbers(): WinningNumbers { - val numbers = Console.readLine().split(NUMBER_DELIMITER).map(String::toInt) + val numbers = runCatching { + Console.readLine().split(NUMBER_DELIMITER).map(String::toInt) + }.onFailure { + throw NumberFormatException(INPUT_NOT_NUMBER) + }.getOrThrow() + return WinningNumbers(numbers = numbers) } @@ -46,11 +51,16 @@ class ConsoleLottoView : LottoView { override fun getBonusNumber(): BonusNumber = BonusNumber(number = readInt()) - private fun readInt(): Int = Console.readLine().toInt() + private fun readInt(): Int = runCatching { + Console.readLine().toInt() + }.onFailure { + throw NumberFormatException(INPUT_NOT_NUMBER) + }.getOrThrow() companion object { const val ERROR_MESSAGE_PREFIX = "[ERROR]" const val DEFAULT_ERROR_MESSAGE = "에러가 발생했습니다. 다시 시도해주세요." const val NUMBER_DELIMITER = ',' + const val INPUT_NOT_NUMBER = "숫자를 입력해 주세요." } } \ No newline at end of file From 9987beb3c4e720f9ea31e364679cbe24a84f0b6e Mon Sep 17 00:00:00 2001 From: ErroredPasta Date: Wed, 8 Nov 2023 21:47:53 +0900 Subject: [PATCH 14/20] =?UTF-8?q?fix:=20BonusNumber=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=EC=8B=9C=20=EC=98=AC=EB=B0=94=EB=A5=B4=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EC=9D=80=20=EB=B2=94=EC=9C=84=EC=9D=98=20=EC=88=AB=EC=9E=90?= =?UTF-8?q?=EA=B0=80=20=EC=A3=BC=EC=96=B4=EC=A7=88=20=EA=B2=BD=EC=9A=B0=20?= =?UTF-8?q?=EC=98=88=EC=99=B8=20=EB=A9=94=EC=8B=9C=EC=A7=80=EC=9D=98=20?= =?UTF-8?q?=EC=98=A4=ED=83=80=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/lotto/domain/model/BonusNumber.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/lotto/domain/model/BonusNumber.kt b/src/main/kotlin/lotto/domain/model/BonusNumber.kt index a251fd8fc..2ebc3dd1c 100644 --- a/src/main/kotlin/lotto/domain/model/BonusNumber.kt +++ b/src/main/kotlin/lotto/domain/model/BonusNumber.kt @@ -7,7 +7,7 @@ value class BonusNumber(val number: Int) { } companion object { - const val NUMBER_NOT_IN_VALID_RANGE_MESSAGE = "숫자가 $${Lotto.MIN_NUMBER}~${Lotto.MAX_NUMBER} 사이에 존재해야 합니다." + const val NUMBER_NOT_IN_VALID_RANGE_MESSAGE = "숫자가 ${Lotto.MIN_NUMBER}~${Lotto.MAX_NUMBER} 사이에 존재해야 합니다." const val NUMBER_EXISTS_IN_WINNING_NUMBERS = "이미 숫자가 당첨 번호에 존재합니다." } } \ No newline at end of file From 1d0e7726d41fa31800d1602b3bf4722090ee3c31 Mon Sep 17 00:00:00 2001 From: ErroredPasta Date: Wed, 8 Nov 2023 22:57:22 +0900 Subject: [PATCH 15/20] =?UTF-8?q?feat:=20=EC=83=9D=EC=84=B1=ED=95=9C=20?= =?UTF-8?q?=EB=A1=9C=EB=98=90=EC=99=80=20=EC=9E=85=EB=A0=A5=EB=B0=9B?= =?UTF-8?q?=EC=9D=80=20=EB=8B=B9=EC=B2=A8=20=EB=B2=88=ED=98=B8=EC=99=80=20?= =?UTF-8?q?=EB=B3=B4=EB=84=88=EC=8A=A4=20=EB=B2=88=ED=98=B8=EB=A5=BC=20?= =?UTF-8?q?=EC=9D=B4=EC=9A=A9=ED=95=98=EC=97=AC=20=EA=B2=B0=EA=B3=BC?= =?UTF-8?q?=EB=A5=BC=20=EA=B3=84=EC=82=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/lotto/LottoSimulator.kt | 1 + src/main/kotlin/lotto/domain/model/Lotto.kt | 19 ++++++++++++ src/main/kotlin/lotto/domain/model/Result.kt | 10 +++++++ .../lotto/ui/presenter/LottoPresenter.kt | 10 +++++++ .../kotlin/lotto/ui/view/ConsoleLottoView.kt | 29 +++++++++++++++++++ src/main/kotlin/lotto/ui/view/LottoView.kt | 3 ++ 6 files changed, 72 insertions(+) create mode 100644 src/main/kotlin/lotto/domain/model/Result.kt diff --git a/src/main/kotlin/lotto/LottoSimulator.kt b/src/main/kotlin/lotto/LottoSimulator.kt index f3c85218d..e83ad73d5 100644 --- a/src/main/kotlin/lotto/LottoSimulator.kt +++ b/src/main/kotlin/lotto/LottoSimulator.kt @@ -14,5 +14,6 @@ class LottoSimulator( view.onGetMoneyDone() view.onGetLottoesDone() view.onGetWinningNumbersDone() + view.getBonusNumberDone() } } \ No newline at end of file diff --git a/src/main/kotlin/lotto/domain/model/Lotto.kt b/src/main/kotlin/lotto/domain/model/Lotto.kt index 305b4bcfa..360d70f63 100644 --- a/src/main/kotlin/lotto/domain/model/Lotto.kt +++ b/src/main/kotlin/lotto/domain/model/Lotto.kt @@ -1,5 +1,7 @@ package lotto.domain.model +import lotto.domain.model.Result.* + class Lotto(private val numbers: List) { init { require(numbers.size == NUMBER_OF_LOTTO_NUMBERS) { MISMATCH_NUMBER_OF_LOTTO_NUMBERS_MESSAGE } @@ -19,6 +21,23 @@ class Lotto(private val numbers: List) { override fun toString(): String = numbers.toString() + fun calculateResult(winningNumbers: WinningNumbers, bonusNumber: BonusNumber): Result { + val sizeOfNumbersInCommon = numbers.intersect(winningNumbers.numbers.toSet()).size + + return when (sizeOfNumbersInCommon) { + FIRST_PLACE.matchingNumberCount -> FIRST_PLACE + SECOND_PLACE.matchingNumberCount -> decideSecondOrThirdPlace(bonusNumber = bonusNumber) + FOURTH_PLACE.matchingNumberCount -> FOURTH_PLACE + FIFTH_PLACE.matchingNumberCount -> FIFTH_PLACE + else -> NOTHING + } + } + + private fun decideSecondOrThirdPlace(bonusNumber: BonusNumber): Result { + if (bonusNumber.number in numbers) return SECOND_PLACE + return THIRD_PLACE + } + companion object { const val MIN_NUMBER = 1 const val MAX_NUMBER = 45 diff --git a/src/main/kotlin/lotto/domain/model/Result.kt b/src/main/kotlin/lotto/domain/model/Result.kt new file mode 100644 index 000000000..fcb7ce8f5 --- /dev/null +++ b/src/main/kotlin/lotto/domain/model/Result.kt @@ -0,0 +1,10 @@ +package lotto.domain.model + +enum class Result(val matchingNumberCount: Int, val prize: Int) { + FIRST_PLACE(matchingNumberCount = 6, prize = 2_000_000_000), + SECOND_PLACE(matchingNumberCount = 5, prize = 30_000_000), + THIRD_PLACE(matchingNumberCount = 5, prize = 1_500_000), + FOURTH_PLACE(matchingNumberCount = 4, prize = 50_000), + FIFTH_PLACE(matchingNumberCount = 3, prize = 5_000), + NOTHING(matchingNumberCount = 2, prize = 0) +} \ No newline at end of file diff --git a/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt b/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt index 027e8a541..b64cde85c 100644 --- a/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt +++ b/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt @@ -72,6 +72,16 @@ class LottoPresenter( } } + fun getResults() { + val results = lottoes.map { + it.calculateResult(winningNumbers = winningNumbers, bonusNumber = bonusNumber) + }.groupingBy { result -> + result + }.eachCount() + + view.displayResults(results = results) + } + companion object { const val ENTER_MONEY_MESSAGE = "구입금액을 입력해 주세요." const val NUMBER_OF_BOUGHT_LOTTOES_MESSAGE = "개를 구매했습니다." diff --git a/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt index 69eecdf53..2e082e2de 100644 --- a/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt +++ b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt @@ -3,6 +3,7 @@ package lotto.ui.view import camp.nextstep.edu.missionutils.Console import lotto.domain.model.BonusNumber import lotto.domain.model.Money +import lotto.domain.model.Result import lotto.domain.model.WinningNumbers import lotto.ui.presenter.LottoPresenter @@ -51,6 +52,30 @@ class ConsoleLottoView : LottoView { override fun getBonusNumber(): BonusNumber = BonusNumber(number = readInt()) + override fun getBonusNumberDone() { + presenter.getResults() + } + + override fun displayResults(results: Map) { + val resultsToPrint = Result.values().dropLast(n = 1) // NOTHING은 출력하지 않는다 + + resultsToPrint.forEach { + val resultCount = results[it] ?: 0 + println("${it.convertToString()} - ${resultCount}개") + } + } + + private fun Result.convertToString(): String = buildString { + append(matchingNumberCount) + append(MATCHING_NUMBERS_SUFFIX_MESSAGE) + if (this@convertToString == Result.SECOND_PLACE) append(BONUS_NUMBER_MATCH_MESSAGE) + append(' ') + append('(') + append(PRIZE_FORMAT.format(prize)) + append(KRW) + append(')') + } + private fun readInt(): Int = runCatching { Console.readLine().toInt() }.onFailure { @@ -62,5 +87,9 @@ class ConsoleLottoView : LottoView { const val DEFAULT_ERROR_MESSAGE = "에러가 발생했습니다. 다시 시도해주세요." const val NUMBER_DELIMITER = ',' const val INPUT_NOT_NUMBER = "숫자를 입력해 주세요." + const val MATCHING_NUMBERS_SUFFIX_MESSAGE = "개 일치" + const val BONUS_NUMBER_MATCH_MESSAGE = ", 보너스 볼 일치" + const val PRIZE_FORMAT = "%,d" + const val KRW = "원" } } \ No newline at end of file diff --git a/src/main/kotlin/lotto/ui/view/LottoView.kt b/src/main/kotlin/lotto/ui/view/LottoView.kt index 39bc13b56..42824142c 100644 --- a/src/main/kotlin/lotto/ui/view/LottoView.kt +++ b/src/main/kotlin/lotto/ui/view/LottoView.kt @@ -2,6 +2,7 @@ package lotto.ui.view import lotto.domain.model.BonusNumber import lotto.domain.model.Money +import lotto.domain.model.Result import lotto.domain.model.WinningNumbers import lotto.ui.presenter.LottoPresenter @@ -16,4 +17,6 @@ interface LottoView { fun getWinningNumbers(): WinningNumbers fun onGetWinningNumbersDone() fun getBonusNumber(): BonusNumber + fun getBonusNumberDone() + fun displayResults(results: Map) } \ No newline at end of file From 0661c0ae34eba97954e01ab0ced5a1e7a256bd39 Mon Sep 17 00:00:00 2001 From: ErroredPasta Date: Wed, 8 Nov 2023 23:10:14 +0900 Subject: [PATCH 16/20] =?UTF-8?q?test:=20Lotto=EA=B0=80=20=EB=8B=B9?= =?UTF-8?q?=EC=B2=A8=20=EB=B2=88=ED=98=B8=EC=99=80=20=EB=B3=B4=EB=84=88?= =?UTF-8?q?=EC=8A=A4=20=EB=B2=88=ED=98=B8=EA=B0=80=20=EC=A3=BC=EC=96=B4?= =?UTF-8?q?=EC=A1=8C=EC=9D=84=20=EB=95=8C=20=EA=B2=B0=EA=B3=BC=EB=A5=BC=20?= =?UTF-8?q?=EC=A0=95=ED=99=95=ED=9E=88=20=EA=B3=84=EC=82=B0=ED=95=98?= =?UTF-8?q?=EB=8A=94=EC=A7=80=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/kotlin/lotto/LottoTest.kt | 37 ++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/src/test/kotlin/lotto/LottoTest.kt b/src/test/kotlin/lotto/LottoTest.kt index 8e2fbe24b..1915ecb16 100644 --- a/src/test/kotlin/lotto/LottoTest.kt +++ b/src/test/kotlin/lotto/LottoTest.kt @@ -1,6 +1,10 @@ package lotto +import lotto.domain.model.BonusNumber import lotto.domain.model.Lotto +import lotto.domain.model.Result +import lotto.domain.model.WinningNumbers +import org.assertj.core.api.Assertions.assertThat import org.assertj.core.api.Assertions.assertThatIllegalArgumentException import org.junit.jupiter.api.DisplayName import org.junit.jupiter.api.Test @@ -56,6 +60,27 @@ class LottoTest { .withMessage(Lotto.NUMBERS_NOT_IN_ASCENDING_ORDER) // then } + @ParameterizedTest + @MethodSource("winningNumbersAndBonusNumberAndResult") + @DisplayName("Lotto와 당첨 번호, 보너스 번호가 주어질 경우 결과를 올바르게 계산하는지") + fun calculateResult_winningNumbersAndBonusNumberAreGiven_calculateResultCorrectly( + winningNumbers: List, + bonusNumber: Int, + expected: Result + ) { + // given + val lotto = Lotto(numbers = listOf(1, 2, 3, 4, 5, 6)) + + // when + val result = lotto.calculateResult( + winningNumbers = WinningNumbers(winningNumbers), + bonusNumber = BonusNumber(bonusNumber) + ) + + // then + assertThat(result).isEqualTo(expected) + } + companion object { @JvmStatic private fun provideNumbersSizeIsSmallerThanNumberOfLottoNumbers(): Stream = Stream.of( @@ -74,5 +99,17 @@ class LottoTest { Arguments.of(listOf(1, 2, 4, 3, 5, 6)), Arguments.of(listOf(6, 5, 4, 3, 2, 1)), ) + + @JvmStatic + private fun winningNumbersAndBonusNumberAndResult(): Stream = Stream.of( + Arguments.of(listOf(1, 2, 3, 4, 5, 6), 45, Result.FIRST_PLACE), + Arguments.of(listOf(1, 2, 3, 4, 5, 10), 6, Result.SECOND_PLACE), + Arguments.of(listOf(1, 2, 3, 4, 5, 10), 45, Result.THIRD_PLACE), + Arguments.of(listOf(1, 2, 3, 4, 10, 11), 45, Result.FOURTH_PLACE), + Arguments.of(listOf(1, 2, 3, 10, 11, 12), 45, Result.FIFTH_PLACE), + Arguments.of(listOf(1, 2, 10, 11, 12, 13), 45, Result.NOTHING), + Arguments.of(listOf(1, 10, 11, 12, 13, 14), 45, Result.NOTHING), + Arguments.of(listOf(10, 11, 12, 13, 14, 15), 45, Result.NOTHING), + ) } } From 7383ed0d769646bb0f5f98a33b834af8798fd538 Mon Sep 17 00:00:00 2001 From: ErroredPasta Date: Wed, 8 Nov 2023 23:28:54 +0900 Subject: [PATCH 17/20] =?UTF-8?q?feat:=20=EC=88=98=EC=9D=B5=EB=A5=A0=20?= =?UTF-8?q?=EA=B3=84=EC=82=B0=20=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/lotto/LottoSimulator.kt | 1 + .../lotto/ui/presenter/LottoPresenter.kt | 23 +++++++++++++++---- .../kotlin/lotto/ui/view/ConsoleLottoView.kt | 4 ++++ src/main/kotlin/lotto/ui/view/LottoView.kt | 1 + 4 files changed, 24 insertions(+), 5 deletions(-) diff --git a/src/main/kotlin/lotto/LottoSimulator.kt b/src/main/kotlin/lotto/LottoSimulator.kt index e83ad73d5..1adc9e341 100644 --- a/src/main/kotlin/lotto/LottoSimulator.kt +++ b/src/main/kotlin/lotto/LottoSimulator.kt @@ -15,5 +15,6 @@ class LottoSimulator( view.onGetLottoesDone() view.onGetWinningNumbersDone() view.getBonusNumberDone() + view.onGetResultsDone() } } \ No newline at end of file diff --git a/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt b/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt index b64cde85c..3899687a9 100644 --- a/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt +++ b/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt @@ -1,9 +1,6 @@ package lotto.ui.presenter -import lotto.domain.model.BonusNumber -import lotto.domain.model.Lotto -import lotto.domain.model.Money -import lotto.domain.model.WinningNumbers +import lotto.domain.model.* import lotto.domain.repository.LottoRepository import lotto.ui.view.LottoView @@ -23,6 +20,8 @@ class LottoPresenter( private var _bonusNumber: BonusNumber? = null private val bonusNumber get() = requireNotNull(_bonusNumber) + private lateinit var results: Map + fun getMoney() { view.displayMessage(ENTER_MONEY_MESSAGE) @@ -73,7 +72,7 @@ class LottoPresenter( } fun getResults() { - val results = lottoes.map { + results = lottoes.map { it.calculateResult(winningNumbers = winningNumbers, bonusNumber = bonusNumber) }.groupingBy { result -> result @@ -82,10 +81,24 @@ class LottoPresenter( view.displayResults(results = results) } + fun getProfit() { + var totalPrize = 0.0f + + results.forEach { (result, count) -> + totalPrize += result.prize * count + } + + val profit = (totalPrize / money.amount) * PERCENT + + view.displayMessage(PROFIT_FORMAT.format(profit)) + } + companion object { const val ENTER_MONEY_MESSAGE = "구입금액을 입력해 주세요." const val NUMBER_OF_BOUGHT_LOTTOES_MESSAGE = "개를 구매했습니다." const val ENTER_WINNING_NUMBERS_MESSAGE = "당첨 번호를 입력해 주세요." const val ENTER_BONUS_NUMBER_MESSAGE = "보너스 번호를 입력해 주세요." + const val PERCENT = 100 + const val PROFIT_FORMAT = "총 수익률은 %.1f%%입니다." } } \ No newline at end of file diff --git a/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt index 2e082e2de..7537ed9a6 100644 --- a/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt +++ b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt @@ -76,6 +76,10 @@ class ConsoleLottoView : LottoView { append(')') } + override fun onGetResultsDone() { + presenter.getProfit() + } + private fun readInt(): Int = runCatching { Console.readLine().toInt() }.onFailure { diff --git a/src/main/kotlin/lotto/ui/view/LottoView.kt b/src/main/kotlin/lotto/ui/view/LottoView.kt index 42824142c..6c35ecb82 100644 --- a/src/main/kotlin/lotto/ui/view/LottoView.kt +++ b/src/main/kotlin/lotto/ui/view/LottoView.kt @@ -19,4 +19,5 @@ interface LottoView { fun getBonusNumber(): BonusNumber fun getBonusNumberDone() fun displayResults(results: Map) + fun onGetResultsDone() } \ No newline at end of file From 0c1b37a1a1a24b25f356200c8206f83c7373010e Mon Sep 17 00:00:00 2001 From: ErroredPasta Date: Wed, 8 Nov 2023 23:29:32 +0900 Subject: [PATCH 18/20] =?UTF-8?q?refactor:=20LottoView=EC=9D=98=20getBonus?= =?UTF-8?q?NumberDone=EC=9D=84=20=EC=9D=BC=EA=B4=80=EC=84=B1=20=EC=9E=88?= =?UTF-8?q?=EA=B2=8C=20onGetBonusNumberDone=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/lotto/LottoSimulator.kt | 2 +- src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt | 2 +- src/main/kotlin/lotto/ui/view/LottoView.kt | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/main/kotlin/lotto/LottoSimulator.kt b/src/main/kotlin/lotto/LottoSimulator.kt index 1adc9e341..c218640f8 100644 --- a/src/main/kotlin/lotto/LottoSimulator.kt +++ b/src/main/kotlin/lotto/LottoSimulator.kt @@ -14,7 +14,7 @@ class LottoSimulator( view.onGetMoneyDone() view.onGetLottoesDone() view.onGetWinningNumbersDone() - view.getBonusNumberDone() + view.onGetBonusNumberDone() view.onGetResultsDone() } } \ No newline at end of file diff --git a/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt index 7537ed9a6..e067f737b 100644 --- a/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt +++ b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt @@ -52,7 +52,7 @@ class ConsoleLottoView : LottoView { override fun getBonusNumber(): BonusNumber = BonusNumber(number = readInt()) - override fun getBonusNumberDone() { + override fun onGetBonusNumberDone() { presenter.getResults() } diff --git a/src/main/kotlin/lotto/ui/view/LottoView.kt b/src/main/kotlin/lotto/ui/view/LottoView.kt index 6c35ecb82..d68b80da2 100644 --- a/src/main/kotlin/lotto/ui/view/LottoView.kt +++ b/src/main/kotlin/lotto/ui/view/LottoView.kt @@ -17,7 +17,7 @@ interface LottoView { fun getWinningNumbers(): WinningNumbers fun onGetWinningNumbersDone() fun getBonusNumber(): BonusNumber - fun getBonusNumberDone() + fun onGetBonusNumberDone() fun displayResults(results: Map) fun onGetResultsDone() } \ No newline at end of file From b7290dc2def6948b0a090cb6d9c00168352500b8 Mon Sep 17 00:00:00 2001 From: ErroredPasta Date: Wed, 8 Nov 2023 23:43:44 +0900 Subject: [PATCH 19/20] =?UTF-8?q?fix:=20NoSuchElementException=EC=9D=84=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=ED=95=98=EC=A7=80=20=EC=95=8A=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../kotlin/lotto/ui/presenter/LottoPresenter.kt | 13 ++++++++++--- src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt | 8 ++++---- 2 files changed, 14 insertions(+), 7 deletions(-) diff --git a/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt b/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt index 3899687a9..714bf10d2 100644 --- a/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt +++ b/src/main/kotlin/lotto/ui/presenter/LottoPresenter.kt @@ -29,7 +29,7 @@ class LottoPresenter( view.getMoney() }.onSuccess { _money = it - }.onFailure { error -> + }.onFailureOtherThanNoSuchElementException { error -> view.displayErrorMessage(message = error.message) getMoney() } @@ -50,7 +50,7 @@ class LottoPresenter( view.getWinningNumbers() }.onSuccess { _winningNumbers = it - }.onFailure { error -> + }.onFailureOtherThanNoSuchElementException { error -> view.displayErrorMessage(message = error.message) getWinningNumbers() } @@ -65,7 +65,7 @@ class LottoPresenter( } }.onSuccess { _bonusNumber = it - }.onFailure { error -> + }.onFailureOtherThanNoSuchElementException { error -> view.displayErrorMessage(message = error.message) getBonusNumber() } @@ -93,6 +93,13 @@ class LottoPresenter( view.displayMessage(PROFIT_FORMAT.format(profit)) } + // NoSuchElementException은 테스트에 사용되므로 처리하지 않는다. + private fun kotlin.Result.onFailureOtherThanNoSuchElementException(action: (Throwable) -> Unit) = + onFailure { error -> + if (error is NoSuchElementException) throw error + action(error) + } + companion object { const val ENTER_MONEY_MESSAGE = "구입금액을 입력해 주세요." const val NUMBER_OF_BOUGHT_LOTTOES_MESSAGE = "개를 구매했습니다." diff --git a/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt index e067f737b..94a2de2a9 100644 --- a/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt +++ b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt @@ -39,8 +39,8 @@ class ConsoleLottoView : LottoView { override fun getWinningNumbers(): WinningNumbers { val numbers = runCatching { Console.readLine().split(NUMBER_DELIMITER).map(String::toInt) - }.onFailure { - throw NumberFormatException(INPUT_NOT_NUMBER) + }.onFailure { error -> + if (error is NumberFormatException) throw NumberFormatException(INPUT_NOT_NUMBER) }.getOrThrow() return WinningNumbers(numbers = numbers) @@ -82,8 +82,8 @@ class ConsoleLottoView : LottoView { private fun readInt(): Int = runCatching { Console.readLine().toInt() - }.onFailure { - throw NumberFormatException(INPUT_NOT_NUMBER) + }.onFailure { error -> + if (error is NumberFormatException) throw NumberFormatException(INPUT_NOT_NUMBER) }.getOrThrow() companion object { From e62e3a44818c5c87388e3df50ed31a669bfffe79 Mon Sep 17 00:00:00 2001 From: ErroredPasta Date: Wed, 8 Nov 2023 23:44:04 +0900 Subject: [PATCH 20/20] =?UTF-8?q?refactor:=20Result=EC=97=90=20values?= =?UTF-8?q?=EA=B0=80=20=EC=95=84=EB=8B=88=EB=9D=BC=20entries=EB=A5=BC=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=ED=95=98=EB=8F=84=EB=A1=9D=20=EB=B3=80?= =?UTF-8?q?=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt index 94a2de2a9..5d9f15b9e 100644 --- a/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt +++ b/src/main/kotlin/lotto/ui/view/ConsoleLottoView.kt @@ -57,7 +57,7 @@ class ConsoleLottoView : LottoView { } override fun displayResults(results: Map) { - val resultsToPrint = Result.values().dropLast(n = 1) // NOTHING은 출력하지 않는다 + val resultsToPrint = Result.entries.dropLast(n = 1) // NOTHING은 출력하지 않는다 resultsToPrint.forEach { val resultCount = results[it] ?: 0