Skip to content

Step4 - Lotto (Manual) #1158

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: mingyum-kim
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,11 @@
- [x] Enter bonus number for second prize
- [x] Calculate winning statistics considering bonus number
- [x] Calculate rate considering bonus number
- [x] Check edge case and fix errors
- [x] Check edge case and fix errors

## Step4 - Lotto (Manual)

- [x] Enter number of manual tickets to purchase
- [x] Enter numbers of each manual ticket
- [x] Generate automatic tickets except manual tickets
- [x] Check edge case and fix errors
7 changes: 4 additions & 3 deletions src/main/kotlin/lotto/controller/LottoController.kt
Original file line number Diff line number Diff line change
Expand Up @@ -13,10 +13,11 @@ fun main() {
}

class LottoController(private val inputView: InputView, private val resultView: ResultView) {
fun purchase() : Tickets {
fun purchase(): Tickets {
val purchaseAmount = inputView.enterPurchaseAmount()
val tickets = LottoShop().purchase(purchaseAmount)
resultView.printTickets(tickets)
val manualTickets = inputView.enterManualTickets()
val tickets = LottoShop().purchase(purchaseAmount, manualTickets)
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When we purchase the tickets, a new instance of LottoShop is created each time.
Do you think that's necessary? How else could we handle that?

Here’s a reference you might find helpful:


(+) Similarly, LottoNumber is a value object with a fixed range of values.
It might be worth thinking about how instances are managed.
I recommend revisiting what we covered in the last feedback session!

resultView.printTickets(tickets, manualTickets.size, tickets.size - manualTickets.size)
return tickets
}

Expand Down
6 changes: 3 additions & 3 deletions src/main/kotlin/lotto/domain/LottoShop.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ class LottoShop {
const val PRICE_FOR_ONT_TICKET = 1000
}

fun purchase(purchaseAmount: Int): Tickets {
val amountOfTicket = purchaseAmount / PRICE_FOR_ONT_TICKET
return Tickets(amountOfTicket)
fun purchase(purchaseAmount: Int, manualTickets: List<Ticket>): Tickets {
val numberOfRandomTickets = (purchaseAmount / PRICE_FOR_ONT_TICKET) - manualTickets.size
return Tickets(numberOfRandomTickets, manualTickets)
}
}
2 changes: 1 addition & 1 deletion src/main/kotlin/lotto/domain/MatchingPrize.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ package lotto.domain

enum class MatchPrize(val matchCount: Int, val usingBonus: Boolean, val price: Long) {
FIRST(6, false, 20_000_000_000),
SECOND(5, true,30_000_000),
SECOND(5, true, 30_000_000),
THIRD(5, false, 1_500_000),
FOURTH(4, false, 50_000),
FIFTH(3, false, 5_000);
Expand Down
16 changes: 14 additions & 2 deletions src/main/kotlin/lotto/domain/Ticket.kt
Original file line number Diff line number Diff line change
@@ -1,12 +1,24 @@
package lotto.domain

import lotto.controller.WinningNumbers

class Ticket(val lottoNumber: Set<LottoNumber>) {
constructor() : this(generateLottoNumber())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's a nice approach to distinguish between generating a ticket randomly and manually.
However, I think the current constructor doesn't clearly convey that intent.
How about using a factory function instead?

Here’s a reference you might find helpful:


init {
require(lottoNumber.size == 6) {
throw IllegalArgumentException("Please enter $AMOUNT_OF_NUMBER_FOR_TICKET numbers")
}
}

constructor(input: List<Int>) : this(input.map { LottoNumber(it) }.toSet())
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before merging, it might be good to double-check if the class layout follows the recommended order across the code.


fun contains(bonusNumber: LottoNumber): Boolean {
return lottoNumber.contains(bonusNumber)
fun checkMatches(winningNumbers: WinningNumbers): Int {
return winningNumbers.numbers.intersect(lottoNumber).size
}

fun checkBonusMatches(winningNumbers: WinningNumbers): Boolean {
return lottoNumber.contains(winningNumbers.bonusNumber)
}

companion object {
Expand Down
21 changes: 10 additions & 11 deletions src/main/kotlin/lotto/domain/Tickets.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,29 +6,28 @@ class Tickets(val tickets: List<Ticket>) {
val size: Int
get() = tickets.size

constructor(amountOfTicket: Int) : this(generateTickets(amountOfTicket))
constructor(numberOfRandomTickets: Int, manualTickets: List<Ticket>)
: this(generateTicketsWithManualTickets(numberOfRandomTickets, manualTickets))

fun checkMatches(winningNumbers: WinningNumbers): WinningStatistics {
val statistics = WinningStatistics()
for (ticket in tickets) {
val matches = checkMatches(winningNumbers, ticket)
val bonusMatches = checkBonusMatches(winningNumbers, ticket)
val matches = ticket.checkMatches(winningNumbers)
val bonusMatches = ticket.checkBonusMatches(winningNumbers)
statistics.add(matches, bonusMatches)
Comment on lines +15 to 17
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Continue to #1156 (comment):

Oh, my mistake 🥲 I explained it inappropriately — sorry for the confusion.
What I actually meant to ask was: "Why do we check both matches and bonus matches separately?"

Since both are needed to determine the prize, wouldn’t it make more sense to treat them as a single match result?
Is there a way we could express this in a more cohesive?

}
return statistics
}

private fun checkMatches(winningNumbers: WinningNumbers, ticket: Ticket): Int{
return winningNumbers.numbers.intersect(ticket.lottoNumber).size
}

private fun checkBonusMatches(winningNumbers: WinningNumbers, ticket: Ticket): Boolean {
return ticket.contains(winningNumbers.bonusNumber)
}

companion object {
private fun generateTickets(amountOfTicket: Int): List<Ticket> {
return List(amountOfTicket) { Ticket() }
}

private fun generateTicketsWithManualTickets(numberOfTickets: Int, manualTickets: List<Ticket>): List<Ticket> {
val generatedTickets = manualTickets.toMutableList()
generatedTickets.addAll(generateTickets(numberOfTickets))
return generatedTickets
}
}
}
29 changes: 25 additions & 4 deletions src/main/kotlin/lotto/view/InputView.kt
Original file line number Diff line number Diff line change
@@ -1,21 +1,42 @@
package lotto.view

import lotto.domain.Ticket

class InputView {
fun enterPurchaseAmount(): Int {
println("Please enter the purchase amount.")
return readln().toIntOrNull() ?: throw IllegalArgumentException("Please enter a valid purchase.")
}

fun enterWinningNumbers() : List<Int> {
fun enterWinningNumbers(): List<Int> {
println("\nPlease enter last week’s winning numbers.")
val input = readlnOrNull() ?: throw IllegalArgumentException("Please enter a valid numbers.")
return input.split(",").map { it.trim().toIntOrNull()
?: throw IllegalArgumentException("Please enter a valid numbers.") }
return enterLottoNumbers()
}

fun enterBonusNumber(): Int {
println("Please enter the bonus number.")
val input = readlnOrNull() ?: throw IllegalArgumentException("Please enter a valid number.")
return input.toIntOrNull() ?: throw java.lang.IllegalArgumentException("Please enter a valid number.")
}

fun enterManualTickets(): List<Ticket> {
println("\nEnter the number of manual tickets to purchase:")
val numberOfManualTickets = readlnOrNull()?.toIntOrNull()
?: throw IllegalArgumentException("Please enter a valid number of manual tickets.")

println("\nEnter the numbers for manual tickets.")
val tickets = mutableListOf<Ticket>()
repeat(numberOfManualTickets) {
tickets.add(Ticket(enterLottoNumbers()))
}
return tickets
}

private fun enterLottoNumbers(): List<Int> {
val input = readlnOrNull() ?: throw IllegalArgumentException("Please enter a valid numbers.")
return input.split(",").map {
it.trim().toIntOrNull()
?: throw IllegalArgumentException("Please enter a valid numbers.")
}
}
}
15 changes: 9 additions & 6 deletions src/main/kotlin/lotto/view/ResultView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ import lotto.domain.Tickets
import lotto.domain.WinningStatistics

class ResultView {
fun printTickets(tickets: Tickets) {
println("You have purchased ${tickets.size}")
fun printTickets(tickets: Tickets, numberOfManualTickets: Int, numberOfRandomTickets: Int) {
println("\nPurchased $numberOfManualTickets and $numberOfRandomTickets automatic tickets.")
for (ticket in tickets.tickets) {
printTicket(ticket)
}
Expand All @@ -17,12 +17,15 @@ class ResultView {
println(ticket.lottoNumber.map { it.number }
.joinToString(", ", "[", "]"))
}

fun printWinningStatistics(winningStatistics: WinningStatistics, rate: Double) {
println("\nWinning Statistics\n------------------")
for(prize in MatchPrize.entries) {
println("${prize.matchCount} Matches " +
(if (prize.usingBonus) "+ Bonus Ball " else "") +
"(${prize.price} KRW) - ${winningStatistics.get(prize.matchCount, prize.usingBonus)} tickets")
for (prize in MatchPrize.entries) {
println(
"${prize.matchCount} Matches " +
(if (prize.usingBonus) "+ Bonus Ball " else "") +
"(${prize.price} KRW) - ${winningStatistics.get(prize.matchCount, prize.usingBonus)} tickets"
)
}
println("Total return rate is $rate (A rate below 1 means a loss)")
}
Expand Down
15 changes: 14 additions & 1 deletion src/test/kotlin/lotto/domain/LottoShopTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,20 @@ class LottoShopTest {
@Test
fun `purchase success`() {
val purchaseAmount = 15000
val tickets = LottoShop().purchase(purchaseAmount)
val tickets = LottoShop().purchase(purchaseAmount, listOf())
assertThat(tickets.size).isEqualTo(15)
}

@Test
fun `purchase with mutable tickets`() {
val purchaseAmount = 15000
val tickets = LottoShop().purchase(
purchaseAmount, listOf(
Ticket(listOf(1, 2, 3, 4, 5, 6)),
Ticket(listOf(1, 2, 3, 4, 5, 6)),
Ticket(listOf(1, 2, 3, 4, 5, 6))
)
)
assertThat(tickets.size).isEqualTo(15)
}
}
13 changes: 13 additions & 0 deletions src/test/kotlin/lotto/domain/TicketTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package lotto.domain

import org.junit.jupiter.api.Test
import org.junit.jupiter.api.assertThrows

class TicketTest {
@Test
fun `fail when under 6 numbers is entered`() {
assertThrows<IllegalArgumentException> {
Ticket(listOf(1, 2, 3, 4, 5))
}
}
}