-
Notifications
You must be signed in to change notification settings - Fork 323
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
[Step2] 로또(자동) #601
base: kminkyung
Are you sure you want to change the base?
[Step2] 로또(자동) #601
Changes from all commits
fb06827
d7ac1d0
cd44bc6
3934778
220b4a9
69c4dac
e811399
da51546
c6f9ab5
5837c47
449830d
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 |
---|---|---|
@@ -1 +1 @@ | ||
# kotlin-lotto | ||
# kotlin-lottery |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
# 요구사항 | ||
|
||
## 로또(자동) | ||
* 로또 1장의 가격은 1000원이다. | ||
|
||
### 화면 로직 | ||
- [x] 로또 구입 금액을 입력받을 수 있다. ("구입금액을 입력해 주세요.") | ||
- [x] 로또 구매 결과를 표시할 수 있다. ("14개를 구매했습니다.") | ||
- [x] 지난 주 당첨 번호를 입력받을 수 있다. ("지난 주 당첨 번호를 입력해 주세요.") | ||
|
||
### 비즈니스 로직 | ||
- [x] 구입 금액 입력시 해당하는 로또를 발급할 수 있다. | ||
- [x] 로또는 6개의 숫자를 가진다. | ||
- [x] 로또의 각 숫자는 1 이상 45 이하다. | ||
|
||
- [x] 당첨 결과를 연산할 수 있다. | ||
- [x] 당첨 결과를 표시할 수 있다. -> 당첨 통계 | ||
- [x] 총 수익률을 계산할 수 있다. | ||
- [x] 총 수익률을 표시할 수 있다. "총 수익률은 0.35입니다." |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
package lottery.controller | ||
|
||
import lottery.domain.lotto.Lotto | ||
import lottery.domain.winningresult.WinningResult | ||
import lottery.service.CalculatorService | ||
import lottery.service.LottoService | ||
import lottery.service.WinningResultService | ||
import lottery.ui.ViewService | ||
|
||
class LotteryController( | ||
private val viewService: ViewService, | ||
private val lottoService: LottoService, | ||
private val winningResultService: WinningResultService, | ||
private val calculatorService: CalculatorService, | ||
) { | ||
fun run() { | ||
val amount = viewService.getPurchasingAmount() | ||
val lottos = purchaseLottos(amount) | ||
|
||
val result = drawWinners(lottos) | ||
|
||
reportRateOfReturn(amount, result) | ||
} | ||
|
||
private fun purchaseLottos(amount: Long): List<Lotto> { | ||
return lottoService.issue(amount).also { | ||
viewService.showPurchasingResult(it) | ||
} | ||
} | ||
|
||
private fun drawWinners(lottos: List<Lotto>): WinningResult { | ||
val winningNumber = viewService.getWinningNumber() | ||
return winningResultService.draw(lottos, winningNumber) | ||
.also { | ||
viewService.showResultOfWinning(it) | ||
} | ||
} | ||
|
||
private fun reportRateOfReturn(amount: Long, result: WinningResult) { | ||
val rateOfReturn = calculatorService.rateOfReturn(amount, result) | ||
viewService.showRateOfReturn(rateOfReturn) | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
package lottery.domain.lotto | ||
|
||
class Lotto( | ||
var numbers: List<Int> = emptyList() | ||
) { | ||
init { | ||
if (numbers.isNotEmpty()) { | ||
check(numbers.size == 6) | ||
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. 숫자 6은 어떤 것을 의미할까요? |
||
} else { | ||
createNumbers() | ||
} | ||
} | ||
|
||
private fun createNumbers() { | ||
numbers = numberRange.shuffled().subList(0, LOTTO_SLOT).sorted() | ||
} | ||
Comment on lines
+14
to
+16
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. 로또 번호가 중복되면 어떻게 될까요? |
||
|
||
override fun toString() = "Lotto:$numbers" | ||
|
||
companion object { | ||
private const val LOTTO_SLOT = 6 | ||
private val numberRange = (1..45) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package lottery.domain.winningresult | ||
|
||
typealias NumberOfWins = Int | ||
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. Int타입을 NumberOfWins로 타입의 이름을 변경해주셨네요. ref. https://quickbirdstudios.com/blog/kotlin-value-classes/ |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,10 @@ | ||
package lottery.domain.ranking | ||
|
||
import lottery.domain.winningresult.NumberOfWins | ||
|
||
enum class Ranking(val rank: NumberOfWins, val prize: Int) { | ||
THIRD(3, 5000), | ||
FOURTH(4, 50000), | ||
FIFTH(5, 1500000), | ||
SIXTH(6, 2000000000) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
package lottery.domain.winningresult | ||
|
||
typealias WinningResult = Map<NumberOfWins, Int> |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package lottery | ||
|
||
import lottery.controller.LotteryController | ||
import lottery.service.CalculatorService | ||
import lottery.service.ExchangeService | ||
import lottery.service.LottoService | ||
import lottery.service.WinningResultService | ||
import lottery.ui.InputView | ||
import lottery.ui.ResultView | ||
import lottery.ui.ViewService | ||
|
||
fun main() { | ||
val viewService = ViewService(InputView(), ResultView()) | ||
val lottoService = LottoService(ExchangeService()) | ||
val winningResultService = WinningResultService() | ||
val calculatorService = CalculatorService() | ||
|
||
val lotteryController = LotteryController( | ||
viewService, | ||
lottoService, | ||
winningResultService, | ||
calculatorService | ||
) | ||
|
||
lotteryController.run() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,22 @@ | ||
package lottery.service | ||
|
||
import lottery.domain.ranking.Ranking | ||
import lottery.domain.winningresult.WinningResult | ||
import java.math.RoundingMode | ||
import java.text.DecimalFormat | ||
|
||
class CalculatorService { | ||
fun rateOfReturn(amount: Long, result: WinningResult): String { | ||
val earning = Ranking.values().sumOf { | ||
result[it.rank]!!.times(it.prize) | ||
} | ||
Comment on lines
+10
to
+12
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. CalculatorService 객체에서 Ranking 객체에 직접 접근해서 계산을 해주고 있네요. |
||
|
||
return decimalFormat.format(earning.toDouble().div(amount.toDouble())) | ||
} | ||
|
||
companion object { | ||
private val decimalFormat = DecimalFormat("#.##").also { | ||
it.roundingMode = RoundingMode.DOWN | ||
} | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package lottery.service | ||
|
||
import kotlin.math.floor | ||
|
||
class ExchangeService { | ||
fun calculateQuantity(amount: Long): Int { | ||
check(amount > LOTTO_PRICE) | ||
return floor(amount.toDouble().div(LOTTO_PRICE)).toInt() | ||
} | ||
|
||
companion object { | ||
const val LOTTO_PRICE = 1000 | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
package lottery.service | ||
|
||
import lottery.domain.lotto.Lotto | ||
|
||
class LottoService( | ||
private val exchangeService: ExchangeService | ||
){ | ||
|
||
fun issue(amount: Long): List<Lotto> { | ||
val quantity = exchangeService.calculateQuantity(amount) | ||
return List(quantity) { Lotto() } | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
package lottery.service | ||
|
||
import lottery.domain.lotto.Lotto | ||
import lottery.domain.ranking.Ranking | ||
import lottery.domain.winningresult.WinningResult | ||
|
||
class WinningResultService { | ||
fun draw(lottos: List<Lotto>, winningNumbers: List<Int>): WinningResult { | ||
val result = prepareResult() | ||
|
||
lottos.forEach { lotto -> | ||
val numberOfWins = lotto.numbers.filter { winningNumbers.contains(it) }.size | ||
result[numberOfWins] = result[numberOfWins]?.plus(1) ?: return@forEach | ||
} | ||
|
||
return result | ||
} | ||
|
||
private fun prepareResult(): MutableMap<Int, Int> { | ||
val result = mutableMapOf<Int, Int>() | ||
Ranking.values().forEach { result[it.rank] = 0 } | ||
|
||
return result | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package lottery.ui | ||
|
||
class InputView { | ||
fun getPurchasingAmount(): Long { | ||
println(PURCHASING_AMOUNT_MESSAGE) | ||
return readln().toLong() | ||
} | ||
|
||
fun getWinningNumber(): List<Int> { | ||
println(GET_WINNING_NUMBER_MESSAGE) | ||
return readln() | ||
.split(DELIMITER) | ||
.map { it.toInt() } | ||
} | ||
|
||
companion object { | ||
private const val PURCHASING_AMOUNT_MESSAGE = "구입금액을 입력해 주세요." | ||
private const val GET_WINNING_NUMBER_MESSAGE = "지난 주 당첨 번호를 입력해 주세요." | ||
private const val DELIMITER = "," | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
package lottery.ui | ||
|
||
import lottery.domain.lotto.Lotto | ||
import lottery.domain.ranking.Ranking | ||
import lottery.domain.winningresult.WinningResult | ||
|
||
class ResultView { | ||
fun showPurchasingResult(lottos: List<Lotto>) { | ||
println("${lottos.size}개를 구매했습니다.") | ||
showIssuedLottos(lottos) | ||
} | ||
|
||
private fun showIssuedLottos(lottos: List<Lotto>) { | ||
lottos.forEach { | ||
println(it.numbers) | ||
} | ||
println("\n") | ||
} | ||
|
||
fun showResultOfWinning(winningResult: WinningResult) { | ||
val resultByRank = Ranking.values().map { | ||
"${it.rank}개 일치 (${it.prize}원) - ${winningResult[it.rank]}개" | ||
}.joinToString("\n") | ||
|
||
val statistics = buildString { | ||
append("당첨 통계\n") | ||
append("---------\n") | ||
append(resultByRank) | ||
} | ||
|
||
println(statistics) | ||
} | ||
|
||
fun showRateOfReturn(rateOfReturn: String) { | ||
val totalRateOfReturnMessage = "총 수익률은 ${rateOfReturn}입니다." | ||
println(totalRateOfReturnMessage) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package lottery.ui | ||
|
||
import lottery.domain.lotto.Lotto | ||
import lottery.domain.winningresult.WinningResult | ||
|
||
class ViewService( | ||
private val inputView: InputView, | ||
private val resultView: ResultView, | ||
) { | ||
|
||
fun getPurchasingAmount(): Long { | ||
return inputView.getPurchasingAmount() | ||
} | ||
|
||
fun getWinningNumber(): List<Int> { | ||
return inputView.getWinningNumber() | ||
} | ||
|
||
fun showPurchasingResult(lottos: List<Lotto>) { | ||
return resultView.showPurchasingResult(lottos) | ||
} | ||
|
||
fun showResultOfWinning(result: WinningResult) { | ||
return resultView.showResultOfWinning(result) | ||
} | ||
|
||
fun showRateOfReturn(rateOfReturn: String) { | ||
return resultView.showRateOfReturn(rateOfReturn) | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package lottery.domain.lotto | ||
|
||
import io.kotest.core.spec.style.StringSpec | ||
import io.kotest.inspectors.shouldForAll | ||
import io.kotest.matchers.collections.shouldHaveSize | ||
|
||
class LottoSpec : StringSpec({ | ||
val lotto = Lotto() | ||
|
||
"로또는 6개의 숫자를 가진다" { | ||
lotto.numbers shouldHaveSize 6 | ||
} | ||
|
||
"로또의 각 숫자는 1 이상 45 이하다" { | ||
lotto.numbers.shouldForAll { | ||
it in 1..45 | ||
} | ||
} | ||
|
||
}) |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package lottery.service | ||
|
||
import io.kotest.core.spec.style.BehaviorSpec | ||
import io.kotest.matchers.shouldBe | ||
|
||
class CalculatorServiceSpec : BehaviorSpec({ | ||
|
||
given("수익률 계산 서비스는") { | ||
val calculatorService = CalculatorService() | ||
|
||
When("구입 금액과 로또 결과로부터") { | ||
val amount = 14000L | ||
val winningResult = mapOf( | ||
3 to 1, | ||
4 to 0, | ||
5 to 0, | ||
6 to 0 | ||
) | ||
|
||
val rateOfReturn = calculatorService.rateOfReturn(amount, winningResult) | ||
|
||
Then("수익률을 계산하여 반환한다") { | ||
rateOfReturn shouldBe "0.35" | ||
} | ||
} | ||
} | ||
}) |
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.
컬렉션은 외부에서 주솟값을 바꾸지 않아도 add, remove 함수를 통해 데이터를 조작할 수 있습니다.
컬렉션은 val을 활용해보면 어떨까요?