diff --git a/README.md b/README.md index e63ac69cfc..434e9d42f7 100644 --- a/README.md +++ b/README.md @@ -52,3 +52,9 @@ 추가한 도메인 - 보너스 숫자 ( BonusNumber ) +# Step4 - 로또 수동 구매 ( Lotto ) +추가적인 요구사항 +- 사용자가 수동으로 추첨번호를 입력할 수 있도록 해야한다. +- 입력한 금액, 자동 생성 숫자, 수동 생성 번호를 입력하도록 해야한다. +- 수동 생성 번호는 자동 생성 번호와 중복될 수 없다. +- 구매한 금액에서 수동 생성한 티켓을 제외한 나머지가 생성되어야 한다. diff --git a/src/main/kotlin/camp/nextstep/edu/step/step2/domain/amount/BuyAmount.kt b/src/main/kotlin/camp/nextstep/edu/step/step2/domain/amount/BuyAmount.kt index dfa5b555ae..042e17d4c1 100644 --- a/src/main/kotlin/camp/nextstep/edu/step/step2/domain/amount/BuyAmount.kt +++ b/src/main/kotlin/camp/nextstep/edu/step/step2/domain/amount/BuyAmount.kt @@ -12,8 +12,9 @@ value class BuyAmount( constructor(amount: Long) : this(BigDecimal.valueOf(amount)) - fun divideByLotteryPrice(ticketPrice: Long): Int { - return amount.divide(BigDecimal.valueOf(ticketPrice)).toInt() + fun divideByLotteryPriceAndManualLottoAmount(ticketPrice: Long, manualTicketAmount: Int): Int { + val ticketAmount = amount.divide(BigDecimal.valueOf(ticketPrice)).toInt() + return ticketAmount - manualTicketAmount } } diff --git a/src/main/kotlin/camp/nextstep/edu/step/step2/domain/amount/ManualTicketAmount.kt b/src/main/kotlin/camp/nextstep/edu/step/step2/domain/amount/ManualTicketAmount.kt new file mode 100644 index 0000000000..b69c3575dc --- /dev/null +++ b/src/main/kotlin/camp/nextstep/edu/step/step2/domain/amount/ManualTicketAmount.kt @@ -0,0 +1,12 @@ +package camp.nextstep.edu.step.step2.domain.amount + +@JvmInline +value class ManualTicketAmount( + val amount: Long +) { + + init { + require(amount > 0) { "구매 금액은 0보다 커야 합니다." } + } + +} diff --git a/src/main/kotlin/camp/nextstep/edu/step/step2/domain/lotto/Lotto.kt b/src/main/kotlin/camp/nextstep/edu/step/step2/domain/lotto/Lotto.kt index 85b5110f9e..272299a7cf 100644 --- a/src/main/kotlin/camp/nextstep/edu/step/step2/domain/lotto/Lotto.kt +++ b/src/main/kotlin/camp/nextstep/edu/step/step2/domain/lotto/Lotto.kt @@ -10,6 +10,10 @@ class Lotto( return numbers.count { targetLotto.numbers.contains(it) } } + fun isDuplicateNumber(anotherNumber: Number): Boolean { + return numbers.contains(anotherNumber) + } + fun countMatchBonus(bonusNumber: Number): Int { return numbers.count { it.number == bonusNumber.number } } diff --git a/src/main/kotlin/camp/nextstep/edu/step/step2/domain/lotto/Lottos.kt b/src/main/kotlin/camp/nextstep/edu/step/step2/domain/lotto/Lottos.kt index 5bd0e01d7a..c34ee8b999 100644 --- a/src/main/kotlin/camp/nextstep/edu/step/step2/domain/lotto/Lottos.kt +++ b/src/main/kotlin/camp/nextstep/edu/step/step2/domain/lotto/Lottos.kt @@ -1,5 +1,6 @@ package camp.nextstep.edu.step.step2.domain.lotto +import camp.nextstep.edu.step.step2.domain.number.Number import camp.nextstep.edu.step.step2.domain.result.LottoMatch import java.util.stream.Collectors @@ -11,16 +12,16 @@ class Lottos( require(lottos.isNotEmpty()) { "로또는 1개 이상이어야 합니다." } } - fun checkLottoNumbersByWinningLotto(winningLotto: WinningLotto): List { - val lottoMatchResults = mutableListOf() + fun isContainLottoNumbers(numbers: List): Boolean { + return lottos.contains(Lotto(numbers = numbers)) + } - for (lotto in lottos) { + fun checkLottoNumbersByWinningLotto(winningLotto: WinningLotto): List { + return lottos.map { lotto -> val matchNumbers = lotto.countMatch(winningLotto.winningLotto) val matchBonus = lotto.countMatchBonus(winningLotto.bonusNumber) - lottoMatchResults.add(LottoMatch.of(matchNumbers, matchBonus)) + LottoMatch.of(matchNumbers, matchBonus) } - return lottoMatchResults - } fun getLottoElements(): List> { return lottos.stream() diff --git a/src/main/kotlin/camp/nextstep/edu/step/step2/domain/lotto/WinningLotto.kt b/src/main/kotlin/camp/nextstep/edu/step/step2/domain/lotto/WinningLotto.kt index aa32108f40..e5eaf7a821 100644 --- a/src/main/kotlin/camp/nextstep/edu/step/step2/domain/lotto/WinningLotto.kt +++ b/src/main/kotlin/camp/nextstep/edu/step/step2/domain/lotto/WinningLotto.kt @@ -9,8 +9,9 @@ data class WinningLotto( init { require(winningLotto.numbers.isNotEmpty()) { "당첨번호가 입력되지 않았습니다." } + // winningLotto 내 Number들과 bonusNumber가 중복되는지 검사 - require(!winningLotto.numbers.contains(bonusNumber)) { "보너스 번호는 당첨 번호와 중복될 수 없습니다." } + require(!winningLotto.isDuplicateNumber(bonusNumber)) { "보너스 번호는 당첨 번호와 중복될 수 없습니다." } } } diff --git a/src/main/kotlin/camp/nextstep/edu/step/step2/domain/result/LottoMatch.kt b/src/main/kotlin/camp/nextstep/edu/step/step2/domain/result/LottoMatch.kt index 89cc18f701..08d7e37ea5 100644 --- a/src/main/kotlin/camp/nextstep/edu/step/step2/domain/result/LottoMatch.kt +++ b/src/main/kotlin/camp/nextstep/edu/step/step2/domain/result/LottoMatch.kt @@ -8,14 +8,15 @@ enum class LottoMatch( SIX_MATCH(6, 0, 2_000_000_000), FIVE_MATCH(5, 0, 1_500_000), FIVE_MATCH_WITH_BONUS(5, 1, 30_000_000), - FOUR_MATCH(4, 0,50_000), - THREE_MATCH(3, 0,5_000), - NONE(0, 0,0); + FOUR_MATCH(4, 0, 50_000), + THREE_MATCH(3, 0, 5_000), + NONE(0, 0, 0); companion object { fun of(matchCount: Int, bonusMatch: Int): LottoMatch { - return values().find { it.matchCount == matchCount && it.bonusMatch == bonusMatch } ?: NONE + return values().find { it.matchCount == matchCount && it.bonusMatch == bonusMatch } + ?: NONE } } diff --git a/src/main/kotlin/camp/nextstep/edu/step/step2/domain/store/LottoStore.kt b/src/main/kotlin/camp/nextstep/edu/step/step2/domain/store/LottoStore.kt index 70d88b6392..ab7e67f7bc 100644 --- a/src/main/kotlin/camp/nextstep/edu/step/step2/domain/store/LottoStore.kt +++ b/src/main/kotlin/camp/nextstep/edu/step/step2/domain/store/LottoStore.kt @@ -20,20 +20,31 @@ object LottoStore { /** * @description : 구매금액에 따른 로또 티켓 수량을 구한다. */ - fun buyLottoTickets(buyAmount: BuyAmount): LottoTicketAmount { - val lottoTicketAmount = buyAmount.divideByLotteryPrice(ticketPrice = LOTTO_TICKET_PRICE) + fun buyAutoLottoTickets(buyAmount: BuyAmount, manualTicketAmount: Int): LottoTicketAmount { + val lottoTicketAmount = buyAmount.divideByLotteryPriceAndManualLottoAmount( + ticketPrice = LOTTO_TICKET_PRICE, + manualTicketAmount = manualTicketAmount + ) + return LottoTicketAmount(lottoTicketAmount) } /** * @description : 로또 티켓 수량에 따른 로또 번호를 생성한다. */ - fun createNumbersByLottoTicketAmount(ticketAmount: LottoTicketAmount): Lottos { + fun createNumbersByLottoTicketAmount( + ticketAmount: LottoTicketAmount, + manualLottos: Lottos + ): Lottos { val lottoTickets = mutableListOf() for (i in 1..ticketAmount.lottoTicketAmount) { - val numbers = NumberGenerator.generate(NumberGenerator.LOTTO_RANDOM, START_NUMBER, END_NUMBER) - lottoTickets.add(Lotto(numbers = numbers)) + val numbers = + NumberGenerator.generate(NumberGenerator.LOTTO_RANDOM, START_NUMBER, END_NUMBER) + + if (!manualLottos.isContainLottoNumbers(numbers)) { + lottoTickets.add(Lotto(numbers = numbers)) + } } return Lottos(lottos = lottoTickets) diff --git a/src/main/kotlin/camp/nextstep/edu/step/step2/dto/LottoProcessDto.kt b/src/main/kotlin/camp/nextstep/edu/step/step2/dto/LottoProcessDto.kt new file mode 100644 index 0000000000..2ff09b8c80 --- /dev/null +++ b/src/main/kotlin/camp/nextstep/edu/step/step2/dto/LottoProcessDto.kt @@ -0,0 +1,7 @@ +package camp.nextstep.edu.step.step2.dto + +data class LottoProcessDto( + val manualTicketAmount: Int, + val autoTicketAmount: Int, + val lottoTicketList: List>, +) diff --git a/src/main/kotlin/camp/nextstep/edu/step/step2/dto/LottoResultDto.kt b/src/main/kotlin/camp/nextstep/edu/step/step2/dto/LottoResultDto.kt index f8f13e8ae4..59c5a798cf 100644 --- a/src/main/kotlin/camp/nextstep/edu/step/step2/dto/LottoResultDto.kt +++ b/src/main/kotlin/camp/nextstep/edu/step/step2/dto/LottoResultDto.kt @@ -1,9 +1,9 @@ package camp.nextstep.edu.step.step2.dto -data class LottoResultDto ( +data class LottoResultDto( val matchResponse: List, val lottoProfitRate: Double - ) { +) { data class MatchResponse( val matchCount: Int, diff --git a/src/main/kotlin/camp/nextstep/edu/step/step2/generator/NumberGenerator.kt b/src/main/kotlin/camp/nextstep/edu/step/step2/generator/NumberGenerator.kt index c89716b3f9..8e1e123c69 100644 --- a/src/main/kotlin/camp/nextstep/edu/step/step2/generator/NumberGenerator.kt +++ b/src/main/kotlin/camp/nextstep/edu/step/step2/generator/NumberGenerator.kt @@ -5,12 +5,14 @@ import camp.nextstep.edu.step.step2.domain.number.Number enum class NumberGenerator( val generate: (Int, Int) -> List ) { - LOTTO_RANDOM({ startNum, endNum -> (startNum..endNum).shuffled().subList(0, 6).distinct().sorted() }); + LOTTO_RANDOM({ startNum, endNum -> + (startNum..endNum).shuffled().subList(0, 6).distinct().sorted() + }); companion object { fun generate(generator: NumberGenerator, startNumber: Int, endNumber: Int): List { - return generator.generate(startNumber, endNumber).map { Number(it) } + return generator.generate(startNumber, endNumber).map { Number(it) } } } diff --git a/src/main/kotlin/camp/nextstep/edu/step/step2/main.kt b/src/main/kotlin/camp/nextstep/edu/step/step2/main.kt index 216e548566..b4f1a53f80 100644 --- a/src/main/kotlin/camp/nextstep/edu/step/step2/main.kt +++ b/src/main/kotlin/camp/nextstep/edu/step/step2/main.kt @@ -4,28 +4,40 @@ import camp.nextstep.edu.step.step2.domain.amount.BuyAmount import camp.nextstep.edu.step.step2.domain.lotto.Lottos import camp.nextstep.edu.step.step2.domain.lotto.WinningLotto import camp.nextstep.edu.step.step2.domain.store.LottoStore +import camp.nextstep.edu.step.step2.dto.LottoProcessDto import camp.nextstep.edu.step.step2.view.InputView import camp.nextstep.edu.step.step2.view.OutputView fun main() { val buyAmount = InputView.getInputValueAndReturnBuyAmount() + val manualLottoCount = InputView.inputManualLottoCount() + val manualLottos = InputView.inputManalLottoNumbers(ticketAmount = manualLottoCount.amount) - val lottos = buyLottoTicketsWithAmount(buyAmount = buyAmount) + val lottos = buyLottoTicketsWithAmount(buyAmount = buyAmount, manualLottos = manualLottos) checkLottoTicketsWinningResult(lottos = lottos) } -private fun buyLottoTicketsWithAmount(buyAmount: BuyAmount): Lottos { - val tickets = LottoStore.buyLottoTickets(buyAmount = buyAmount) - - val lottos = LottoStore.createNumbersByLottoTicketAmount(ticketAmount = tickets) +private fun buyLottoTicketsWithAmount(buyAmount: BuyAmount, manualLottos: Lottos): Lottos { + val tickets = LottoStore.buyAutoLottoTickets( + buyAmount = buyAmount, + manualTicketAmount = manualLottos.getLottoSize() + ) + val lottos = LottoStore.createNumbersByLottoTicketAmount( + ticketAmount = tickets, + manualLottos = manualLottos + ) - val ticketElements = lottos.getLottoElements() + val autoLottoNumbers = lottos.getLottoElements() + val manualLottoNumbers = manualLottos.getLottoElements() OutputView.displayTicketsNumbers( - ticketsAmount = tickets.lottoTicketAmount, - ticketNumbers = ticketElements + LottoProcessDto( + manualTicketAmount = manualLottos.getLottoSize(), + autoTicketAmount = tickets.lottoTicketAmount, + lottoTicketList = manualLottoNumbers + autoLottoNumbers + ) ) return lottos diff --git a/src/main/kotlin/camp/nextstep/edu/step/step2/view/InputView.kt b/src/main/kotlin/camp/nextstep/edu/step/step2/view/InputView.kt index c5d6b8a160..1ca11cd7e2 100644 --- a/src/main/kotlin/camp/nextstep/edu/step/step2/view/InputView.kt +++ b/src/main/kotlin/camp/nextstep/edu/step/step2/view/InputView.kt @@ -1,7 +1,9 @@ package camp.nextstep.edu.step.step2.view import camp.nextstep.edu.step.step2.domain.amount.BuyAmount +import camp.nextstep.edu.step.step2.domain.amount.ManualTicketAmount import camp.nextstep.edu.step.step2.domain.lotto.Lotto +import camp.nextstep.edu.step.step2.domain.lotto.Lottos import camp.nextstep.edu.step.step2.domain.number.Number object InputView { @@ -11,6 +13,17 @@ object InputView { return BuyAmount(readLine()!!.toLong()) } + fun inputManualLottoCount(): ManualTicketAmount { + println("수동으로 구매할 로또 수를 입력해 주세요.") + return ManualTicketAmount(amount = readLine()!!.toLong()) + } + + fun inputManalLottoNumbers(ticketAmount: Long): Lottos { + println("수동으로 구매할 번호를 입력해 주세요.") + val lottoList = (1..ticketAmount).map { Lotto.ofInputValues(numbers = readLine()!!) } + return Lottos(lottos = lottoList) + } + fun inputLastWeekWinningNumbers(): Lotto { println("지난 주 당첨 번호를 입력해 주세요.") return Lotto.ofInputValues(numbers = readLine()!!) diff --git a/src/main/kotlin/camp/nextstep/edu/step/step2/view/OutputView.kt b/src/main/kotlin/camp/nextstep/edu/step/step2/view/OutputView.kt index 19027e16f5..ad8539ebed 100644 --- a/src/main/kotlin/camp/nextstep/edu/step/step2/view/OutputView.kt +++ b/src/main/kotlin/camp/nextstep/edu/step/step2/view/OutputView.kt @@ -1,15 +1,17 @@ package camp.nextstep.edu.step.step2.view +import camp.nextstep.edu.step.step2.dto.LottoProcessDto import camp.nextstep.edu.step.step2.dto.LottoResultDto object OutputView { - fun displayTicketsNumbers(ticketsAmount: Int, ticketNumbers: List>) { - println("${ticketsAmount}개를 구매했습니다.") + fun displayTicketsNumbers(lottoProcessDto: LottoProcessDto) { + println("수동으로 ${lottoProcessDto.manualTicketAmount}장, 자동으로 ${lottoProcessDto.autoTicketAmount}개를 구매했습니다.") - for (ticketNumber in ticketNumbers) { - println(ticketNumber) + for (lottoTicket in lottoProcessDto.lottoTicketList) { + println(lottoTicket) } + } fun displayLottoResultByDto(lottoResultDto: LottoResultDto) { diff --git a/src/test/kotlin/camp/nextstep/edu/step/step2/domain/store/LottoStoreTest.kt b/src/test/kotlin/camp/nextstep/edu/step/step2/domain/store/LottoStoreTest.kt index e5efe70e52..032c687f2e 100644 --- a/src/test/kotlin/camp/nextstep/edu/step/step2/domain/store/LottoStoreTest.kt +++ b/src/test/kotlin/camp/nextstep/edu/step/step2/domain/store/LottoStoreTest.kt @@ -15,14 +15,15 @@ import java.math.BigDecimal @DisplayName("로또 상점은") class LottoStoreTest : BehaviorSpec({ - Given("구입 금액이 주어지고") { + Given("구입 금액과 수동 로또 개수가 주어지고") { val buyAmount = BuyAmount(amount = BigDecimal.valueOf(10000L)) + val manualTicketAmount = 2 When("로또를 구매하면") { - val lottoTickets = LottoStore.buyLottoTickets(buyAmount) + val lottoTickets = LottoStore.buyAutoLottoTickets(buyAmount, manualTicketAmount) Then("구매한 금액만큼의 로또 티켓 수를 반환한다.") { - lottoTickets.lottoTicketAmount shouldBe 10 + lottoTickets.lottoTicketAmount shouldBe 8 } } } @@ -82,12 +83,24 @@ class LottoStoreTest : BehaviorSpec({ } } - Given("로또의 수량이주어지고") { + Given("로또의 수량과 수동 로또번호가 주어지고") { val lottoTicketAmount = LottoTicketAmount(lottoTicketAmount = 10) + val manualLotto = Lottos( lottos = listOf( + Lotto( + listOf( + Number(number = 1), + Number(number = 2), + Number(number = 3), + Number(number = 4), + Number(number = 5), + Number(number = 6) + ) + ) + )) When("로또 발급을 시도하면") { val lottoTickets = - LottoStore.createNumbersByLottoTicketAmount(ticketAmount = lottoTicketAmount) + LottoStore.createNumbersByLottoTicketAmount(ticketAmount = lottoTicketAmount, manualLottos = manualLotto) Then("구매한 수량만큼의 로또가 발급된다.") { lottoTickets.lottos.size shouldBe 10 @@ -148,5 +161,4 @@ class LottoStoreTest : BehaviorSpec({ } } } - })