-
Notifications
You must be signed in to change notification settings - Fork 355
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
base: pkch93
Are you sure you want to change the base?
[step4] 로또(수동) #1086
Changes from all commits
4cf65f1
7f20969
2ffd2d3
16394ab
6925a5a
a47d246
fae5a39
cc5389b
4ed952a
cb0f39f
83ad28b
03c6ed7
8fe4f35
f0e2a72
70d5106
fbd493e
b840a01
4e170f1
f197544
2c6e0c6
f693b09
15bd358
5e3171d
c273005
aeb792f
18a7554
d56b835
706bfb5
462051b
b40af46
266f7c4
fea91c0
913819e
bf27d04
737d8de
99e682f
5b73231
599dde4
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 }) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저도 인터페이스 정의할 거 없이 함수로 정의해도 될거 같아서 위와 같이 반영했습니다 There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 👍 |
||
; | ||
|
||
companion object { | ||
|
@@ -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 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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 } | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
|
||
val matchBonusNumber = bonusNumber.isMatch(lotto) | ||
|
||
return Reward.of(matchingNumberCount, matchBonusNumber) | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,7 +9,7 @@ import lotto.WinningLotto | |
|
||
class InputView { | ||
fun input(): BoughtLotto { | ||
val lottos = inputMoney() | ||
val lottos = inputLottoCost() | ||
val winningLotto = inputWinningLotto() | ||
println() | ||
return BoughtLotto( | ||
|
@@ -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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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() | ||
|
@@ -63,7 +105,7 @@ class InputView { | |
?.split(", ") | ||
?.map { it.toInt() } | ||
?: throw IllegalArgumentException("지난 주 당첨 번호는 필수입니다.") | ||
Lotto(winningNumbers) | ||
Lotto.manual(winningNumbers) | ||
} catch (e: NumberFormatException) { | ||
throw IllegalArgumentException("지난 주 당첨 번호는 숫자만 입력 가능합니다.") | ||
} | ||
|
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) | ||
} | ||
} | ||
}) |
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))) | ||
} | ||
} | ||
}) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이 클래스에서 상수로 표현하기 적합한 값들이 몇몇 보이는 것 같습니다.