diff --git a/README.md b/README.md index b47169a70..561e80927 100644 --- a/README.md +++ b/README.md @@ -108,17 +108,52 @@ ##### `요구사항 정리` - 보너스 볼 - - [ ] 보너스 볼을 입력 받을수 있다 - - [ ] 보너스 볼은 당첨 로또 티켓의 6개 번호와 중복 될 수 없다 + - [x] 보너스 볼을 입력 받을수 있다 + - [x] 보너스 볼은 당첨 로또 티켓의 6개 번호와 중복 될 수 없다 - 당첨 로또 - - [ ] 당첨 로또는 1개의 로또 티켓과 1개의 보너스 번호를 가진다 - - [ ] 로또 랭크 계산 역할을 담당한다 + - [x] 당첨 로또는 1개의 로또 티켓과 1개의 보너스 번호를 가진다 + - [x] 로또 랭크 계산 역할을 담당한다 - 로또 티켓 - - [ ] 로또 랭크 계산 역할을 당첨 로또에게 위임한다 + - [x] 로또 랭크 계산 역할을 당첨 로또에게 위임한다 - 로또 랭크 - - [ ] 당첨번호와 5개 일치하고 보너스 번호도 같으면 2등이다, - - [ ] 당첨번호와 5개 일치하고 보너스 번호가 다르면 3등이다 - - [ ] 로또 2등의 당첨금액은 1_500_000원 -> 30_000_000원 - - [ ] 로또 3등의 당첨금액은 50_000원 -> 1_500_000원 - - [ ] 로또 4등의 당첨금액은 5_000원 -> 50_000원 - - [ ] 로또 5등의 당첨금액은 5_000원 + - [x] 당첨번호와 5개 일치하고 보너스 번호도 같으면 2등이다, + - [x] 당첨번호와 5개 일치하고 보너스 번호가 다르면 3등이다 + - [x] 로또 2등의 당첨금액은 1_500_000원 -> 30_000_000원 + - [x] 로또 3등의 당첨금액은 50_000원 -> 1_500_000원 + - [x] 로또 4등의 당첨금액은 5_000원 -> 50_000원 + - [x] 로또 5등의 당첨금액은 5_000원 + +--- + +### STEP4 로또 수동 + +##### `기능 요구사항` +- 현재 로또 생성기는 자동 생성 기능만 제공한다. +- 사용자가 수동으로 추첨 번호를 입력할 수 있도록 해야 한다. +- 입력한 금액, 자동 생성 숫자, 수동 생성 번호를 입력하도록 해야 한다. + +##### `실행 결과` +```text +구입금액을 입력해 주세요. +14000 + +---------추가부분-------------- +수동으로 구매할 로또 수를 입력해 주세요. +3 + +수동으로 구매할 번호를 입력해 주세요. +8, 21, 23, 41, 42, 43 +3, 5, 11, 16, 32, 38 +7, 11, 16, 35, 36, 44 + +수동으로 3장, 자동으로 11개를 구매했습니다. +-------------------------------- +[8, 21, 23, 41, 42, 43] +지난 주 당첨 번호를 입력해 주세요. +1, 2, 3, 4, 5, 6 +보너스 볼을 입력해 주세요. +7 + +당첨 통계 +[... 생략 ...] +``` diff --git a/src/main/kotlin/lotto/LottoApplication.kt b/src/main/kotlin/lotto/LottoApplication.kt index 35bb002f5..1bf7850bf 100644 --- a/src/main/kotlin/lotto/LottoApplication.kt +++ b/src/main/kotlin/lotto/LottoApplication.kt @@ -1,14 +1,7 @@ package lotto import lotto.controller.LottoController -import lotto.view.InputView -import lotto.view.OutputView fun main() { - val amount = InputView.getUserAmount() - val lottoTickets = LottoController.purchaseLotto(amount) - OutputView.printPurchaseResult(lottoTickets) - val winningLotto = InputView.getUserWinningLotto() - val lottoResults = LottoController.calculateLottoRank(lottoTickets, winningLotto) - OutputView.printResults(lottoResults, amount) + LottoController.startLottoGame() } diff --git a/src/main/kotlin/lotto/controller/LottoController.kt b/src/main/kotlin/lotto/controller/LottoController.kt index 996d3c762..ae23a0ecc 100644 --- a/src/main/kotlin/lotto/controller/LottoController.kt +++ b/src/main/kotlin/lotto/controller/LottoController.kt @@ -1,18 +1,17 @@ package lotto.controller -import lotto.domain.LottoResults import lotto.domain.LottoTickets -import lotto.domain.WinningLotto +import lotto.view.InputView +import lotto.view.OutputView object LottoController { - fun purchaseLotto(amount: Int): LottoTickets { - return LottoTickets.purchase(amount) - } - - fun calculateLottoRank( - lottoTickets: LottoTickets, - winningLotto: WinningLotto, - ): LottoResults { - return lottoTickets.calculateLottoRank(winningLotto) + fun startLottoGame() { + val purchasedDetail = InputView.getPurchaseDetail() + val autoLottoTickets = LottoTickets.generateAutoLottoTickets(purchasedDetail.autoLottoQuantity) + val lottoTickets = LottoTickets(purchasedDetail.manualLottoTickets + autoLottoTickets) + OutputView.printPurchaseResult(lottoTickets, purchasedDetail) + val winningLotto = InputView.getUserWinningLotto() + val lottoResults = lottoTickets.calculateLottoRank(winningLotto) + OutputView.printResults(lottoResults, purchasedDetail.purchaseAmount) } } diff --git a/src/main/kotlin/lotto/domain/LottoNumber.kt b/src/main/kotlin/lotto/domain/LottoNumber.kt index 1c15a4f74..28729d07b 100644 --- a/src/main/kotlin/lotto/domain/LottoNumber.kt +++ b/src/main/kotlin/lotto/domain/LottoNumber.kt @@ -1,6 +1,8 @@ package lotto.domain -data class LottoNumber(val number: Int) { +data class LottoNumber(private val number: Int) { + fun getNumber() = number + companion object { const val LOTTO_MIN_NUMBER = 1 const val LOTTO_MAX_NUMBER = 45 diff --git a/src/main/kotlin/lotto/domain/LottoTicket.kt b/src/main/kotlin/lotto/domain/LottoTicket.kt index 2a6094245..53429862a 100644 --- a/src/main/kotlin/lotto/domain/LottoTicket.kt +++ b/src/main/kotlin/lotto/domain/LottoTicket.kt @@ -18,6 +18,7 @@ data class LottoTicket(private val numbers: Set) : Collection) : Collection): LottoTicket { return LottoTicket(numbers.map { LottoNumber(it) }.toSet()) } diff --git a/src/main/kotlin/lotto/domain/LottoTickets.kt b/src/main/kotlin/lotto/domain/LottoTickets.kt index 112aaedb6..72e2dfa52 100644 --- a/src/main/kotlin/lotto/domain/LottoTickets.kt +++ b/src/main/kotlin/lotto/domain/LottoTickets.kt @@ -19,7 +19,7 @@ class LottoTickets(private val lottoTickets: List) : Collection= LOTTO_TICKET_PRICE) { "구입금액은 ${LOTTO_TICKET_PRICE}원 이상이여야 합니다" } @@ -27,5 +27,13 @@ class LottoTickets(private val lottoTickets: List) : Collection 0) { + val lottoTickets = List(autoLottoQuantity) { LottoTicket.generateLottoNumber() } + return LottoTickets(lottoTickets) + } + return LottoTickets(listOf()) + } } } diff --git a/src/main/kotlin/lotto/domain/Money.kt b/src/main/kotlin/lotto/domain/Money.kt new file mode 100644 index 000000000..95dadf3d9 --- /dev/null +++ b/src/main/kotlin/lotto/domain/Money.kt @@ -0,0 +1,5 @@ +package lotto.domain + +class Money(money: String) { + val amount: Int = money.toIntOrNull() ?: throw IllegalArgumentException("구입 금액이 유효하지 않습니다. 숫자를 입력해주세요") +} diff --git a/src/main/kotlin/lotto/dto/PurchaseDetail.kt b/src/main/kotlin/lotto/dto/PurchaseDetail.kt new file mode 100644 index 000000000..9dd53bd38 --- /dev/null +++ b/src/main/kotlin/lotto/dto/PurchaseDetail.kt @@ -0,0 +1,40 @@ +package lotto.dto + +import lotto.domain.LottoTickets +import lotto.domain.LottoTickets.Companion.LOTTO_TICKET_PRICE +import lotto.domain.Money + +data class PurchaseDetail( + val purchaseAmount: Int, + val autoLottoQuantity: Int, + val manualLottoQuantity: Int, + val manualLottoTickets: LottoTickets, +) { + init { + require(manualLottoQuantity * LOTTO_TICKET_PRICE <= purchaseAmount) { "수동으로 구입할 로또의 수량이 구입금액보다 많습니다" } + require(autoLottoQuantity <= purchaseAmount / LOTTO_TICKET_PRICE) { "자동으로 구입할 로또의 수량이 구입금액보다 많습니다" } + } + constructor( + money: Money, + manualLottoTickets: LottoTickets, + ) : this( + money.amount, + money.amount / LOTTO_TICKET_PRICE - manualLottoTickets.size, + manualLottoTickets.size, + manualLottoTickets, + ) + + companion object { + fun of( + money: Money, + manualLottoTickets: LottoTickets, + ): PurchaseDetail { + return PurchaseDetail( + money.amount, + money.amount / LOTTO_TICKET_PRICE - manualLottoTickets.size, + manualLottoTickets.size, + manualLottoTickets, + ) + } + } +} diff --git a/src/main/kotlin/lotto/view/InputView.kt b/src/main/kotlin/lotto/view/InputView.kt index dfc956b97..d0ae14351 100644 --- a/src/main/kotlin/lotto/view/InputView.kt +++ b/src/main/kotlin/lotto/view/InputView.kt @@ -2,19 +2,47 @@ package lotto.view import lotto.domain.LottoNumber import lotto.domain.LottoTicket +import lotto.domain.LottoTickets +import lotto.domain.Money import lotto.domain.WinningLotto +import lotto.dto.PurchaseDetail object InputView { - fun getUserAmount(): Int { + private const val DELIMITER = "," + + fun getPurchaseDetail(): PurchaseDetail { + val money = getPurchaseAmount() + val manualLottoQuantity = getManualLottoQuantity() + val manualLottoTickets = getManualLottoTickets(manualLottoQuantity) + return PurchaseDetail.of(money, manualLottoTickets) + } + + private fun getManualLottoTickets(manualLottoQuantity: Int): LottoTickets { + println("수동으로 구매할 번호를 입력해 주세요.") + val manualLottoTickets = + List(manualLottoQuantity) { + val readLine = readlnOrNull()?.takeIf { it.isNotBlank() } ?: throw IllegalArgumentException("로또 번호를 입력해주세요.") + LottoTicket.makeLottoTicket(readLine) + } + return LottoTickets(manualLottoTickets) + } + + private fun getManualLottoQuantity(): Int { + println("수동으로 구매할 로또 수를 입력해 주세요.") + val quantity = readln() + return quantity.toIntOrNull() ?: throw IllegalArgumentException("유효한 숫자를 입력해주세요") + } + + private fun getPurchaseAmount(): Money { println("구입 금액을 입력해 주세요.") val amount = readln() - return amount.toIntOrNull() ?: throw IllegalArgumentException("구입 금액이 유효하지 않습니다. 숫자를 입력해주세요") + return Money(amount) } fun getUserWinningLotto(): WinningLotto { println("지난 주 당첨 번호를 입력해 주세요.") val winningLottoNumbers: String = readln() - val numbers = winningLottoNumbers.split(",").map { it.toInt() }.toSet() + val numbers = winningLottoNumbers.split(DELIMITER).map { it.toInt() }.toSet() println("보너스 볼을 입력해 주세요.") val bonusNumber: String = readln() diff --git a/src/main/kotlin/lotto/view/OutputView.kt b/src/main/kotlin/lotto/view/OutputView.kt index a4e093945..05ef112ff 100644 --- a/src/main/kotlin/lotto/view/OutputView.kt +++ b/src/main/kotlin/lotto/view/OutputView.kt @@ -4,13 +4,18 @@ import lotto.domain.LottoRank import lotto.domain.LottoResult import lotto.domain.LottoResults import lotto.domain.LottoTickets +import lotto.dto.PurchaseDetail object OutputView { - fun printPurchaseResult(lottoTickets: LottoTickets) { - val ticketCount = lottoTickets.size - println(message = "$ticketCount 개를 구매했습니다.") + fun printPurchaseResult( + lottoTickets: LottoTickets, + purchasedDetail: PurchaseDetail, + ) { + val manualLottoQuantity = purchasedDetail.manualLottoQuantity + val autoLottoQuantity = purchasedDetail.autoLottoQuantity + println("수동으로 ${manualLottoQuantity}장, 자동으로 ${autoLottoQuantity}개를 구매했습니다.") for (lottoTicket in lottoTickets) { - println(lottoTicket.map { it.number }) + println(lottoTicket.map { it.getNumber() }) } } diff --git a/src/test/kotlin/lotto/controller/LottoControllerTest.kt b/src/test/kotlin/lotto/controller/LottoControllerTest.kt deleted file mode 100644 index ae9323ac4..000000000 --- a/src/test/kotlin/lotto/controller/LottoControllerTest.kt +++ /dev/null @@ -1,30 +0,0 @@ -package lotto.controller - -import io.kotest.matchers.shouldBe -import lotto.domain.LottoNumber -import lotto.domain.LottoRank -import lotto.domain.LottoTicket -import lotto.domain.LottoTickets -import lotto.domain.WinningLotto -import org.junit.jupiter.api.Test - -class LottoControllerTest { - @Test - fun `구입 금액을 입력하면 로또 티켓이 천원당 1장으로 여러개의 로또 티켓이 발급된다`() { - val lottoTickets = LottoController.purchaseLotto(10000) - lottoTickets.size shouldBe 10 - } - - @Test - fun `로또 티켓 목록과 당첨 티켓을 전달하면 당첨 결과를 확인 할 수 있다`() { - val lottoTickets = LottoTickets(listOf(LottoTicket.from(setOf(1, 2, 3, 4, 5, 6)))) - val winningLotto = WinningLotto(LottoTicket.from(setOf(1, 2, 3, 4, 5, 6)), LottoNumber.from(7)) - val lottoResults = LottoController.calculateLottoRank(lottoTickets, winningLotto) - lottoResults.findAllSortedByPrize() - .sortedByDescending { it.count } - .take(1) - .map { - it.rank shouldBe LottoRank.FIRST_PLACE - } - } -} diff --git a/src/test/kotlin/lotto/domain/LottoTicketTest.kt b/src/test/kotlin/lotto/domain/LottoTicketTest.kt index a16abdf1d..82b30c5c5 100644 --- a/src/test/kotlin/lotto/domain/LottoTicketTest.kt +++ b/src/test/kotlin/lotto/domain/LottoTicketTest.kt @@ -2,6 +2,7 @@ package lotto.domain import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.collections.shouldHaveSize +import io.kotest.matchers.equals.shouldBeEqual import io.kotest.matchers.shouldBe import org.junit.jupiter.api.Test import org.junit.jupiter.params.ParameterizedTest @@ -114,6 +115,15 @@ class LottoTicketTest { rank.prize shouldBe 0 } + @Test + fun `같은 숫자와 콤마 구분자로 된 문자열로 생성된 로또 티켓은 동일하다`() { + val numbers = "1,2,3,4,5,6" + val lottoTicket = LottoTicket.makeLottoTicket(numbers) + val number2 = "1,2,3,4,5,6" + val lottoTicket2 = LottoTicket.makeLottoTicket(number2) + lottoTicket shouldBeEqual lottoTicket2 + } + companion object { @JvmStatic fun providedDuplicationNumbers() = diff --git a/src/test/kotlin/lotto/domain/LottoTicketsTest.kt b/src/test/kotlin/lotto/domain/LottoTicketsTest.kt index 4b8aa7add..ba248dc53 100644 --- a/src/test/kotlin/lotto/domain/LottoTicketsTest.kt +++ b/src/test/kotlin/lotto/domain/LottoTicketsTest.kt @@ -1,24 +1,19 @@ package lotto.domain -import io.kotest.assertions.throwables.shouldThrow import io.kotest.matchers.collections.shouldHaveSize import io.kotest.matchers.shouldBe import org.junit.jupiter.api.Test class LottoTicketsTest { @Test - fun `로또 티켓은 구입금액은 1_000원 이상이여야 한다`() { - shouldThrow { - LottoTickets.purchase(900) - }.also { - it.message shouldBe "구입금액은 1000원 이상이여야 합니다" - } + fun `자동 로또 티켓은 구입 수량이 0개이상이여야 발급되는 로또 티켓은 0개이다`() { + val autoLottoTickets = LottoTickets.generateAutoLottoTickets(0) + autoLottoTickets.size shouldBe 0 } @Test - fun `로또 티켓을 구입 금액에 맞게 발급한다`() { - val amount = 5_000 - val lottoTickets = LottoTickets.purchase(amount) + fun `로또 티켓을 구입 수량에 맞게 발급한다`() { + val lottoTickets = LottoTickets.generateAutoLottoTickets(5) lottoTickets shouldHaveSize 5 } diff --git a/src/test/kotlin/lotto/dto/PurchaseDetailTest.kt b/src/test/kotlin/lotto/dto/PurchaseDetailTest.kt new file mode 100644 index 000000000..eb161d01e --- /dev/null +++ b/src/test/kotlin/lotto/dto/PurchaseDetailTest.kt @@ -0,0 +1,67 @@ +package lotto.dto + +import io.kotest.assertions.throwables.shouldThrow +import io.kotest.matchers.shouldBe +import lotto.domain.LottoTicket +import lotto.domain.LottoTickets +import lotto.domain.Money +import org.junit.jupiter.api.Test + +class PurchaseDetailTest { + @Test + fun `수동으로 구입할 로또의 수량은 구입금액보다 많을 수 없다`() { + shouldThrow { + PurchaseDetail( + money = Money("1000"), + manualLottoTickets = + LottoTickets( + listOf( + LottoTicket.from(setOf(1, 2, 3, 4, 5, 7)), + LottoTicket.from(setOf(1, 2, 3, 4, 5, 9)), + LottoTicket.from(setOf(1, 2, 3, 4, 8, 9)), + ), + ), + ) + }.also { + it.message shouldBe "수동으로 구입할 로또의 수량이 구입금액보다 많습니다" + } + } + + @Test + fun `자동으로 구입할 로또의 수량은 구입금액보다 많을 수 없다`() { + shouldThrow { + PurchaseDetail( + purchaseAmount = 5_000, + autoLottoQuantity = 10, + manualLottoQuantity = 3, + manualLottoTickets = + LottoTickets( + listOf( + LottoTicket.from(setOf(1, 2, 3, 4, 5, 7)), + LottoTicket.from(setOf(1, 2, 3, 4, 5, 9)), + LottoTicket.from(setOf(1, 2, 3, 4, 8, 9)), + ), + ), + ) + }.also { + it.message shouldBe "자동으로 구입할 로또의 수량이 구입금액보다 많습니다" + } + } + + @Test + fun `만원 구매금액으로 수동 로또 3장을 구입하면 자동 로또는 7장이다`() { + val purchaseDetail = + PurchaseDetail.of( + money = Money("10000"), + manualLottoTickets = + LottoTickets( + listOf( + LottoTicket.from(setOf(1, 2, 3, 4, 5, 7)), + LottoTicket.from(setOf(1, 2, 3, 4, 5, 9)), + LottoTicket.from(setOf(1, 2, 3, 4, 8, 9)), + ), + ), + ) + purchaseDetail.autoLottoQuantity shouldBe 7 + } +}