Skip to content
New issue

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

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

Already on GitHub? Sign in to your account

[step4] 로또(수동) #1086

Open
wants to merge 38 commits into
base: pkch93
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
38 commits
Select commit Hold shift + click to select a range
4cf65f1
[step1] 문자열 덧셈 계산기 테스트 정의
pkch93 Nov 20, 2024
7f20969
[step1] 문자열 덧셈 계산기 테스트 통과하도록 구현
pkch93 Nov 22, 2024
2ffd2d3
[step1] 예외처리 테스트 추가
pkch93 Nov 22, 2024
16394ab
Merge branch 'pkch93' of https://github.com/next-step/kotlin-lotto in…
pkch93 Nov 23, 2024
6925a5a
[step1] 코드리뷰 반영
pkch93 Nov 24, 2024
a47d246
[step1] 코드리뷰 반영
pkch93 Nov 24, 2024
fae5a39
[step1] 코드리뷰 반영
pkch93 Nov 24, 2024
cc5389b
[step2] README 정리
pkch93 Nov 24, 2024
4ed952a
[step2] 로또 테스트 정의
pkch93 Nov 23, 2024
cb0f39f
[step2] 로또 유효성 검증 로직 추가
pkch93 Nov 23, 2024
83ad28b
[step2] input 로직 추가 및 BoughtLotto, WinningLotto 추가
pkch93 Nov 23, 2024
03c6ed7
[step2] 로또 당첨 번호 구현
pkch93 Nov 24, 2024
8fe4f35
[step2] BoughtLottoTest 정의
pkch93 Nov 24, 2024
f0e2a72
[step2] BoughtLottoTest 통과 하도록 구현
pkch93 Nov 24, 2024
70d5106
[step2] BoughtLottoTest 리펙토링
pkch93 Nov 24, 2024
fbd493e
[step2] LottoResult 테스트 추가
pkch93 Nov 24, 2024
b840a01
[step2] LottoResult 수익률 계산 로직 추가
pkch93 Nov 24, 2024
4e170f1
[step2] 로또 결과 출력 구현
pkch93 Nov 24, 2024
f197544
[step2] 로또 번호 유효성 추가
pkch93 Nov 24, 2024
2c6e0c6
[step2] Reward, LottoResult 별도 파일로 분리
pkch93 Nov 24, 2024
f693b09
Merge branch 'pkch93' of https://github.com/next-step/kotlin-lotto in…
pkch93 Nov 25, 2024
15bd358
[step2] 구입 금액 LottoCost 객체로 표현
pkch93 Nov 25, 2024
5e3171d
[step2] 로또 번호 LottoNumber로 추상화
pkch93 Nov 25, 2024
c273005
[step3] 보너스 볼 테스트 추가
pkch93 Nov 25, 2024
aeb792f
[step3] 보너스 볼 구현 추가
pkch93 Nov 25, 2024
18a7554
[step3] WinningLotto에 보너스 볼 필드 추가
pkch93 Nov 25, 2024
d56b835
[step3] 2등 보상 추가
pkch93 Nov 25, 2024
706bfb5
[step3] 2등 보상 출력 구현
pkch93 Nov 25, 2024
462051b
Merge branch 'pkch93' of https://github.com/next-step/kotlin-lotto in…
pkch93 Nov 26, 2024
b40af46
[step3] 리뷰 내용 반영
pkch93 Nov 26, 2024
266f7c4
[step3] 리뷰 내용 반영
pkch93 Nov 26, 2024
fea91c0
[step4] Lotto 수동 생성용 팩토리 메서드 manual 추가
pkch93 Nov 26, 2024
913819e
[step4] 수동 로또 입력 구현
pkch93 Nov 26, 2024
bf27d04
[step4] LottoCost에 manualLottoAmount, autoLottoAmount 추가
pkch93 Nov 26, 2024
737d8de
[step4] LottoCost에서 수동 로또 구입 가능 갯수가 구입 가능 갯수를 초과할때 에러 케이스 테스트 추가
pkch93 Nov 26, 2024
99e682f
[step4] LottoCost에서 수동 로또 구입 가능 갯수가 구입 가능 갯수를 초과시 예외상황 구현
pkch93 Nov 26, 2024
5b73231
[step4] 보너스 볼이 당첨 로또 번호와 중복되는 경우 예외 케이스 테스트 추가
pkch93 Nov 26, 2024
599dde4
[step4] 보너스 볼이 당첨 로또 번호와 중복되는 경우 예외 처리 구현
pkch93 Nov 26, 2024
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion src/main/kotlin/lotto/Lotto.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package lotto

class Lotto(
class Lotto
Copy link

Choose a reason for hiding this comment

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

이 클래스에서 상수로 표현하기 적합한 값들이 몇몇 보이는 것 같습니다.

private constructor(
numbers: List<Int>,
) {
val numbers: List<LottoNumber>
Expand All @@ -19,5 +20,7 @@ class Lotto(
.sorted()
return Lotto(numbers)
}

fun manual(numbers: List<Int>): Lotto = Lotto(numbers)
}
}
11 changes: 5 additions & 6 deletions src/main/kotlin/lotto/LottoCost.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@ package lotto

const val LOTTO_PRICE = 1000

@JvmInline
value class LottoCost(
class LottoCost(
val value: Int,
val manualLottoAmount: Int,
) {
val autoLottoAmount: Int = value / LOTTO_PRICE - manualLottoAmount

init {
require(this.value >= 0) { "구입 금액은 유효한 양수로 입력해야합니다." }
}

fun calculateBoughtLottoAmount(): Int {
return value / LOTTO_PRICE
require(this.autoLottoAmount >= 0) { "구입 가능한 로또 갯수를 초과했습니다." }
}
}
19 changes: 8 additions & 11 deletions src/main/kotlin/lotto/Reward.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,14 @@ package lotto
enum class Reward(
val money: Int,
val matchingNumberCount: Int,
val needMatchBonus: Boolean,
private val isMatch: (matchingNumberCount: Int, matchBonusNumber: Boolean) -> Boolean,
) {
FIRST(2_000_000_000, 6, false),
SECOND(30_000_000, 5, true),
THIRD(1_500_000, 5, false),
FOURTH(50_000, 4, false),
FIFTH(5_000, 3, false),
NONE(0, 0, false)
FIRST(2_000_000_000, 6, { matchingNumberCount, _ -> matchingNumberCount == 6 }) ,
SECOND(30_000_000, 5, { matchingNumberCount, matchBonusNumber -> matchingNumberCount == 5 && matchBonusNumber }),
THIRD(1_500_000, 5, { matchingNumberCount, _ -> matchingNumberCount == 5 }),
FOURTH(50_000, 4, { matchingNumberCount, _ -> matchingNumberCount == 4 }),
FIFTH(5_000, 3, { matchingNumberCount, _ -> matchingNumberCount == 3 }),
NONE(0, 0, { matchingNumberCount, _ -> matchingNumberCount < 3 })
Copy link
Author

Choose a reason for hiding this comment

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

#1072 (comment)

저도 needMatchBonus 부분이 꺼림찍하긴 했는데 이 구현이 좀더 나은거 같네요 :)

인터페이스 정의할 거 없이 함수로 정의해도 될거 같아서 위와 같이 반영했습니다

Copy link

Choose a reason for hiding this comment

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

👍

;

companion object {
Expand All @@ -19,10 +19,7 @@ enum class Reward(
matchBonusNumber: Boolean,
): Reward =
entries.filter { it != NONE }
.find {
it.matchingNumberCount == matchingNumberCount &&
(!it.needMatchBonus || matchBonusNumber)
}
.find { it.isMatch(matchingNumberCount, matchBonusNumber) }
?: NONE
}
}
6 changes: 5 additions & 1 deletion src/main/kotlin/lotto/WinningLotto.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,14 @@ class WinningLotto(
private val lotto: Lotto,
private val bonusNumber: BonusNumber,
) {
init {
require(bonusNumber.value !in lotto.numbers) { "보너스 볼 번호는 당첨 번호와 중복될 수 없습니다." }
}

fun match(lotto: Lotto): Reward {
val matchingNumberCount = this.lotto
.numbers
.count { lotto.numbers.contains(it) }
.count { it in lotto.numbers }
Copy link
Author

Choose a reason for hiding this comment

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

#1072 (comment)

in으로 표현하는게 더 읽기 편해서 위와 같이 수정했어요

val matchBonusNumber = bonusNumber.isMatch(lotto)

return Reward.of(matchingNumberCount, matchBonusNumber)
Expand Down
64 changes: 53 additions & 11 deletions src/main/kotlin/lotto/view/InputView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ import lotto.WinningLotto

class InputView {
fun input(): BoughtLotto {
val lottos = inputMoney()
val lottos = inputLottoCost()
val winningLotto = inputWinningLotto()
println()
return BoughtLotto(
Expand All @@ -18,26 +18,68 @@ class InputView {
)
}

private fun inputMoney(): List<Lotto> {
private fun inputLottoCost(): List<Lotto> {
val money = inputMoney()
val manualLottoAmount = inputManualLottoAmount()
val lottoCost = LottoCost(money, manualLottoAmount)
return generateLottos(lottoCost)
}

private fun inputMoney(): Int {
println("구입금액을 입력해 주세요.")
val maybeMoney = readlnOrNull()
val lottoCost = try {
return try {
requireNotNull(maybeMoney) { "구입 금액은 필수입니다." }
LottoCost(maybeMoney.toInt())
maybeMoney.toInt()
} catch (e: NumberFormatException) {
throw IllegalArgumentException("구입 금액은 숫자만 입력가능합니다.")
}
return generateLottos(lottoCost)
}

private fun inputManualLottoAmount(): Int {
println("수동으로 구매할 로또 수를 입력해주세요.")
try {
val maybeManualLottoAmount = readlnOrNull() ?: throw IllegalArgumentException("수동으로 구매할 로또 수는 필수입니다.")
return maybeManualLottoAmount.toInt()
} catch (e: NumberFormatException) {
throw IllegalArgumentException("수동으로 구매할 로또 수는 숫자만 입력 가능합니다.")
}
}

private fun generateLottos(lottoCost: LottoCost): List<Lotto> {
val boughtLottoAmount = lottoCost.calculateBoughtLottoAmount()
val lottos = (1..boughtLottoAmount).map { Lotto.auto() }
printBoughtLottos(lottos)
return lottos
val manualLottos = inputManualLottoNumbers(lottoCost)

val autoLottoAmount = lottoCost.autoLottoAmount
val autoLottos = (1..autoLottoAmount).map { Lotto.auto() }

val generatedLottos = manualLottos + autoLottos
printBoughtLottos(lottoCost, generatedLottos)
return generatedLottos
Comment on lines +50 to +57
Copy link

Choose a reason for hiding this comment

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

이러한 부분은 view에서 구현하기 적합해 보이지 않는것 같습니다.

view의 종류가 늘어나면 종류가 늘어남과 동시에 위와 같이 생성하는 로직 또한 계속해서 늘어나지 않을까요?

로또 애플리케이션의 중요한 정책으로 보이는데 domain 영역에서 구현해보는게 어떨까요?

}

private fun inputManualLottoNumbers(lottoCost: LottoCost): List<Lotto> {
println("수동으로 구매할 번호를 입력해주세요.")
val maybeManualLottoNumbers = (1..lottoCost.manualLottoAmount)
.map { readlnOrNull() }

return maybeManualLottoNumbers.map {
try {
val manualLottoNumbers = it
?.split(", ")
?.map { manualLottoNumber -> manualLottoNumber.toInt() }
?: throw IllegalArgumentException("수동 로또 번호는 입력은 필수입니다.")
Lotto.manual(manualLottoNumbers)
} catch (e: NumberFormatException) {
throw IllegalArgumentException("수동 로또 번호는 숫자만 입력 가능합니다.")
}
}
}

private fun printBoughtLottos(lottos: List<Lotto>) {
private fun printBoughtLottos(
lottoCost: LottoCost,
lottos: List<Lotto>,
) {
println("수동으로 ${lottoCost.manualLottoAmount}장, 자동으로 ${lottoCost.autoLottoAmount}장 구매했습니다.")
lottos.forEach {
val lottoNumbersString = it.numbers.joinToString(", ") {
lottoNumber -> lottoNumber.value.toString()
Expand All @@ -63,7 +105,7 @@ class InputView {
?.split(", ")
?.map { it.toInt() }
?: throw IllegalArgumentException("지난 주 당첨 번호는 필수입니다.")
Lotto(winningNumbers)
Lotto.manual(winningNumbers)
} catch (e: NumberFormatException) {
throw IllegalArgumentException("지난 주 당첨 번호는 숫자만 입력 가능합니다.")
}
Expand Down
2 changes: 1 addition & 1 deletion src/test/kotlin/lotto/BonusNumberTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ class BonusNumberTest : StringSpec({
"로또 번호와 일치하는지 확인한다." {
val sut = BonusNumber(LottoNumber(1))

val lotto = Lotto(listOf(1, 2, 3, 4, 5, 6))
val lotto = Lotto.manual(listOf(1, 2, 3, 4, 5, 6))
val actual = sut.isMatch(lotto)

actual shouldBe true
Expand Down
14 changes: 7 additions & 7 deletions src/test/kotlin/lotto/BoughtLottoTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ import io.kotest.matchers.shouldBe
class BoughtLottoTest : StringSpec({
"구입한 로또에 대한 결과를 계산한다." {
val lottos = listOf(
Lotto(listOf(1, 2, 3, 4, 5, 6)),
Lotto(listOf(1, 2, 3, 4, 5, 7)),
Lotto(listOf(1, 2, 3, 4, 5, 8)),
Lotto(listOf(1, 2, 3, 4, 10, 11)),
Lotto(listOf(1, 8, 9, 10, 11, 12)),
Lotto(listOf(1, 8, 9, 10, 11, 13)),
Lotto.manual(listOf(1, 2, 3, 4, 5, 6)),
Lotto.manual(listOf(1, 2, 3, 4, 5, 7)),
Lotto.manual(listOf(1, 2, 3, 4, 5, 8)),
Lotto.manual(listOf(1, 2, 3, 4, 10, 11)),
Lotto.manual(listOf(1, 8, 9, 10, 11, 12)),
Lotto.manual(listOf(1, 8, 9, 10, 11, 13)),
)
val winningLotto = WinningLotto(Lotto(listOf(1, 2, 3, 4, 5, 6)), BonusNumber(LottoNumber(7)))
val winningLotto = WinningLotto(Lotto.manual(listOf(1, 2, 3, 4, 5, 6)), BonusNumber(LottoNumber(7)))
val sut = BoughtLotto(lottos, winningLotto)

val actual = sut.matchResult()
Expand Down
30 changes: 19 additions & 11 deletions src/test/kotlin/lotto/LottoCostTest.kt
Original file line number Diff line number Diff line change
@@ -1,31 +1,39 @@
package lotto

import io.kotest.assertions.throwables.shouldThrowMessage
import io.kotest.assertions.throwables.shouldThrowWithMessage
import io.kotest.core.spec.style.StringSpec
import io.kotest.inspectors.forAll
import io.kotest.data.forAll
import io.kotest.data.row
import io.kotest.matchers.shouldBe

class LottoCostTest : StringSpec({
"구입 금액을 생성한다." {
LottoCost(1000)
LottoCost(1000, 0)
}


"구입 금액이 음수라면 예외를 던진다." {
shouldThrowMessage("구입 금액은 유효한 양수로 입력해야합니다.") {
LottoCost(-1000)
LottoCost(-1000, 0)
}
}

"로또 구입 갯수를 계산한다." {
val lottoCosts = listOf(
LottoCost(1000) to 1,
LottoCost(1500) to 1,
LottoCost(10000) to 10,
)
"자동 로또 구입 갯수를 계산한다." {
forAll(
row(LottoCost(1000, 0), 1),
row(LottoCost(1000, 1), 0),
row(LottoCost(1500, 0), 1),
row(LottoCost(1500, 1), 0),
row(LottoCost(10000, 2), 8),
) { lottoCost, expected ->
lottoCost.autoLottoAmount shouldBe expected
}
}

lottoCosts.forAll { (lottoCost, expected) ->
lottoCost.calculateBoughtLottoAmount() shouldBe expected
"수동 구입 갯수가 구입 가능 숫자를 능가하면 예외를 던진다." {
shouldThrowWithMessage<IllegalArgumentException>("구입 가능한 로또 갯수를 초과했습니다.") {
LottoCost(1000, 2)
}
}
})
8 changes: 4 additions & 4 deletions src/test/kotlin/lotto/LottoTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -7,19 +7,19 @@ import io.kotest.matchers.shouldBe
class LottoTest : StringSpec({

"로또를 생성한다." {
val lotto = Lotto(listOf(1, 2, 3, 4, 5, 6))
lotto.numbers shouldBe Lotto(listOf(1, 2, 3, 4, 5, 6)).numbers
val lotto = Lotto.manual(listOf(1, 2, 3, 4, 5, 6))
lotto.numbers shouldBe Lotto.manual(listOf(1, 2, 3, 4, 5, 6)).numbers
}

"로또 번호가 6개 미만이라면 예외를 던진다." {
shouldThrowWithMessage<IllegalArgumentException>("로또 번호는 6개여야 합니다.") {
Lotto(listOf(1, 2, 3, 4, 5))
Lotto.manual(listOf(1, 2, 3, 4, 5))
}
}

"로또 번호 중 중복된 수가 있다면 예외를 던진다." {
shouldThrowWithMessage<IllegalArgumentException>("중복된 로또 번호가 존재합니다.") {
Lotto(listOf(1, 1, 2, 3, 4, 5))
Lotto.manual(listOf(1, 1, 2, 3, 4, 5))
}
}
})
31 changes: 19 additions & 12 deletions src/test/kotlin/lotto/WinningLottoTest.kt
Original file line number Diff line number Diff line change
@@ -1,61 +1,68 @@
package lotto

import io.kotest.assertions.throwables.shouldThrowWithMessage
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe

class WinningLottoTest : StringSpec({

"번호가 3개 일치하는 경우 5등이다." {
val sut = WinningLotto(Lotto(listOf(1, 2, 3, 4, 5, 6)), BonusNumber(LottoNumber(7)))
val sut = WinningLotto(Lotto.manual(listOf(1, 2, 3, 4, 5, 6)), BonusNumber(LottoNumber(7)))

val lotto = Lotto(listOf(1, 2, 3, 7, 8, 9))
val lotto = Lotto.manual(listOf(1, 2, 3, 7, 8, 9))
val actual = sut.match(lotto)

actual shouldBe Reward.FIFTH
}

"번호가 4개 일치하는 경우 4등이다." {
val sut = WinningLotto(Lotto(listOf(1, 2, 3, 4, 5, 6)), BonusNumber(LottoNumber(7)))
val sut = WinningLotto(Lotto.manual(listOf(1, 2, 3, 4, 5, 6)), BonusNumber(LottoNumber(7)))

val lotto = Lotto(listOf(1, 2, 3, 4, 7, 8))
val lotto = Lotto.manual(listOf(1, 2, 3, 4, 7, 8))
val actual = sut.match(lotto)

actual shouldBe Reward.FOURTH
}

"번호가 5개 일치하는 경우 3등이다." {
val sut = WinningLotto(Lotto(listOf(1, 2, 3, 4, 5, 6)), BonusNumber(LottoNumber(7)))
val sut = WinningLotto(Lotto.manual(listOf(1, 2, 3, 4, 5, 6)), BonusNumber(LottoNumber(7)))

val lotto = Lotto(listOf(1, 2, 3, 4, 5, 8))
val lotto = Lotto.manual(listOf(1, 2, 3, 4, 5, 8))
val actual = sut.match(lotto)

actual shouldBe Reward.THIRD
}

"번호 5개와 보너스 볼이 일치하는 경우 2등이다." {
val sut = WinningLotto(Lotto(listOf(1, 2, 3, 4, 5, 6)), BonusNumber(LottoNumber(7)))
val sut = WinningLotto(Lotto.manual(listOf(1, 2, 3, 4, 5, 6)), BonusNumber(LottoNumber(7)))

val lotto = Lotto(listOf(1, 2, 3, 4, 5, 7))
val lotto = Lotto.manual(listOf(1, 2, 3, 4, 5, 7))
val actual = sut.match(lotto)

actual shouldBe Reward.SECOND
}

"번호가 6개 모두 일치하는 경우 1등이다." {
val sut = WinningLotto(Lotto(listOf(1, 2, 3, 4, 5, 6)), BonusNumber(LottoNumber(7)))
val sut = WinningLotto(Lotto.manual(listOf(1, 2, 3, 4, 5, 6)), BonusNumber(LottoNumber(7)))

val lotto = Lotto(listOf(1, 2, 3, 4, 5, 6))
val lotto = Lotto.manual(listOf(1, 2, 3, 4, 5, 6))
val actual = sut.match(lotto)

actual shouldBe Reward.FIRST
}

"번호가 3개 미만으로 일치하는 경우 보상은 존재하지 않는다." {
val sut = WinningLotto(Lotto(listOf(1, 2, 3, 4, 5, 6)), BonusNumber(LottoNumber(7)))
val sut = WinningLotto(Lotto.manual(listOf(1, 2, 3, 4, 5, 6)), BonusNumber(LottoNumber(7)))

val lotto = Lotto(listOf(1, 2, 7, 8, 9, 10))
val lotto = Lotto.manual(listOf(1, 2, 7, 8, 9, 10))
val actual = sut.match(lotto)

actual shouldBe Reward.NONE
}

"보너스 볼 번호가 당첨 번호 번호와 중복된다면 예외를 던진다." {
shouldThrowWithMessage<IllegalArgumentException>("보너스 볼 번호는 당첨 번호와 중복될 수 없습니다.") {
WinningLotto(Lotto.manual(listOf(1, 2, 3, 4, 5, 6)), BonusNumber(LottoNumber(6)))
}
}
})