-
Notifications
You must be signed in to change notification settings - Fork 103
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
[로또] 신민준 미션 제출합니다. #95
base: main
Are you sure you want to change the base?
Changes from all commits
151f759
78f136f
48c1fba
4e45def
cdb1a46
164f194
acd76f5
c186943
c3b8484
dc9ee68
92d3aca
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,170 @@ | ||
# kotlin-lotto-precourse | ||
|
||
## 구현할 기능 | ||
|
||
- [X] Lotto Class | ||
- [X] 값을 반환하는 getLottoValue() | ||
- [X] 결과값을 갖는 enum class LottoResult | ||
- [X] LottoController | ||
- [X] 랜덤으로 6개의 숫자를 만들고 이를 Lotto에 저장하는 releaseLotto() | ||
- [X] 값을 입력받고 정상적인 입력인지 확인후 저장 | ||
- [X] 입력받은 가격에 따른 로또 구매 | ||
- [X] 로또 결과를 확인 | ||
- [X] 최종 결과값 반환 | ||
- [X] 입력과 결과를 다루는 LottoView | ||
|
||
## 구현 과정 | ||
|
||
### releaseLotto() | ||
|
||
Lotto 목록을 저장하는 ArrayList lottos에 getLottoNumber()를 통해 랜덤 로또 번호를 반환하여 만든 Lotto를 add한다. | ||
|
||
``` | ||
private fun releaseLotto() { | ||
lottos.add(Lotto(getLottoNumber())) | ||
} | ||
|
||
private fun getLottoNumber(): List<Int> { | ||
return Randoms.pickUniqueNumbersInRange(1, 45, 6) | ||
} | ||
``` | ||
|
||
### 로또 구매 | ||
|
||
``` | ||
fun getBuyAmount(): Int { | ||
println(Constant.INPUT_BUY_MESSAGE) | ||
return Console.readLine().toInt() | ||
} | ||
|
||
fun showBoughtLotto(lottoAmount: Int, lottos: ArrayList<Lotto>) { | ||
println("\n$lottoAmount" + Constant.BOUGHT_LOTTO_MESSAGE) | ||
println(lottos.joinToString("\n") { "[" + it.getLottoValue().joinToString(", ") + "]" }) | ||
println() | ||
} | ||
``` | ||
LottoView를 통해 값을 입력받는다. | ||
이때 직접적으로 입출력값을 하드코딩하지 말라했던 2주차 피드백을 참고하여 출력메시지를 Constant 오브젝트에 모두 const val로 별도로 저장해주었다. | ||
|
||
|
||
``` | ||
fun startLotto() { | ||
val lottoView = LottoView() | ||
val buyLottoAmount = divideLottoPrice(lottoView.getBuyAmount()) | ||
releaseLotto(buyLottoAmount) | ||
lottoView.showBoughtLotto(buyLottoAmount, lottos) | ||
} | ||
|
||
private fun divideLottoPrice(inputBuyAmount: Int): Int { | ||
return inputBuyAmount / Constant.LOTTO_PRICE | ||
} | ||
|
||
private fun releaseLotto(buyLottoAmount: Int) { | ||
repeat(buyLottoAmount) { | ||
lottos.add(Lotto(getLottoNumber())) | ||
} | ||
} | ||
``` | ||
입력받은 값을 LOTTO_PRICE로 나누고 이를 통해 도출된 구매 갯수에 따라 releaseLotto()를 통해 Lotto를 발매한다. | ||
|
||
### 로또 결과 정산 | ||
``` | ||
private fun calculateLotto(winnerNumber: List<Int>, specialNumber: Int): List<Int> { | ||
val lottoResult = MutableList(5) { 0 } | ||
lottos.forEach { | ||
when (it.getLottoValue().toSet().minus(winnerNumber.toSet()).size) { | ||
3 -> lottoResult[0]++ | ||
2 -> lottoResult[1]++ | ||
1 -> { | ||
if (it.getLottoValue().contains(specialNumber)) lottoResult[3]++ | ||
else lottoResult[2]++ | ||
} | ||
|
||
0 -> lottoResult[4]++ | ||
} | ||
} | ||
return lottoResult | ||
} | ||
``` | ||
입력받은 로또 당첨 번호와 발매된 로또 번호의 공통 개수 비교 및 보너스 볼 넘버를 비교하고 그 갯수를 누적한다. | ||
|
||
``` | ||
private fun getReturnRate(lottoResult: List<Int>, buyLottoAmount: Int) : Double{ | ||
var sum = 0.0 | ||
sum += Constant.THREE_REWARD * lottoResult[0] | ||
sum += Constant.FOUR_REWARD * lottoResult[1] | ||
sum += Constant.FIVE_REWARD * lottoResult[2] | ||
sum += Constant.FIVE_SPECIAL_REWARD * lottoResult[3] | ||
sum += Constant.SIX_REWARD * lottoResult[4] | ||
return sum/(buyLottoAmount*Constant.LOTTO_PRICE) | ||
} | ||
``` | ||
calculateLotto로 얻어낸 lottoResult를 통해 총 수익률을 구하고 이 값들을 LottoView로 전달해주어 | ||
``` | ||
fun showLottoResult(lottoResult : List<Int>){ | ||
println(Constant.RESULT_MESSAGE) | ||
println(Constant.RESULT_THREE_MATCH.format(lottoResult[0])) | ||
println(Constant.RESULT_FOUR_MATCH.format(lottoResult[1])) | ||
println(Constant.RESULT_FIVE_MATCH.format(lottoResult[2])) | ||
println(Constant.RESULT_FIVE_SPECIAL_MATCH.format(lottoResult[3])) | ||
println(Constant.RESULT_SIX_MATCH.format(lottoResult[4])) | ||
} | ||
|
||
fun showReturnRate(returnRate : Double){ | ||
println("총 수익률은 ${String.format("%.1f", returnRate)}%입니다.") | ||
} | ||
|
||
``` | ||
|
||
이것을 사용자에게 출력한다. | ||
|
||
## enum Class 추가 | ||
``` | ||
enum class LottoResult(val prize: Int) { | ||
THREE(5000), | ||
FOUR(50000), | ||
FIVE(1500000), | ||
FIVE_SPECIAL(30000000), | ||
SIX(2000000000) | ||
} | ||
``` | ||
|
||
## 예외처리 | ||
각 입력에 대해서는 유효하지 않을 경우 해당 입력을 다시 받아야하므로 각 입력에 대해서 | ||
``` | ||
fun getSpecialNumber(winnerNumber: List<Int>): Int { | ||
while (true) { | ||
try { | ||
println(Constant.INPUT_SPECIAL_NUMBER_MESSAGE) | ||
val specialNumber = Console.readLine() | ||
if (checkSpecialNumber(specialNumber, winnerNumber)) return specialNumber.toInt() | ||
} catch (e: IllegalArgumentException) { | ||
println(e.message) | ||
} | ||
} | ||
} | ||
|
||
private fun checkSpecialNumber(specialNumber: String, winnerNumber: List<Int>): Boolean { | ||
try { | ||
if (specialNumber.toInt() in 1..45) { | ||
if (!winnerNumber.contains(specialNumber.toInt())) return true | ||
else throw Exception() | ||
} else throw Exception() | ||
} catch (e: Exception) { | ||
throw IllegalArgumentException(Constant.ERROR_SPECIAL_NUMBER_INVALID_MESSAGE) | ||
} | ||
} | ||
``` | ||
|
||
와 같은 연산을 통해 유효성 검사를 수행한다. | ||
|
||
## 테스트 추가 | ||
``` | ||
@Test | ||
fun `로또 번호가 1에서 45 사이의 숫자가 아니라면 예외가 발생한다`() { | ||
assertThrows<IllegalArgumentException> { | ||
Lotto(listOf(1, 2, 3, 4, 5, 46)) | ||
} | ||
} | ||
``` | ||
로또 번혹가 1~45 사이인지 확인하는 테스트를 추가해주었다. |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,6 @@ | ||
package lotto | ||
|
||
fun main() { | ||
// TODO: 프로그램 구현 | ||
val lottoController = LottoController() | ||
lottoController.startLotto() | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package lotto | ||
|
||
object Constant { | ||
const val INPUT_BUY_MESSAGE = "구입금액을 입력해 주세요." | ||
const val LOTTO_PRICE = 1000 | ||
const val BOUGHT_LOTTO_MESSAGE = "개를 구매했습니다." | ||
const val INPUT_WINNER_NUMBER = "당첨 번호를 입력해 주세요." | ||
const val INPUT_SPECIAL_NUMBER_MESSAGE = "\n보너스 번호를 입력해 주세요." | ||
const val RESULT_MESSAGE = "당첨 통계\n---" | ||
const val RESULT_THREE_MATCH = "3개 일치 (5,000원) - %d개" | ||
const val RESULT_FOUR_MATCH = "4개 일치 (50,000원) - %d개" | ||
const val RESULT_FIVE_MATCH = "5개 일치 (1,500,000원) - %d개" | ||
const val RESULT_FIVE_SPECIAL_MATCH = "5개 일치, 보너스 볼 일치 (30,000,000원) - %d개" | ||
const val RESULT_SIX_MATCH = "6개 일치 (2,000,000,000원) - %d개" | ||
|
||
const val ERROR_SPECIAL_NUMBER_INVALID_MESSAGE = "[ERROR] 보너스 번호는 당첨 번호에 속하지 않은 1부터 45 사이의 정수여야 합니다." | ||
const val ERROR_BUY_AMOUNT_INVALID_MESSAGE = "[ERROR] 구매금액은 1000의 배수인 정수여야 합니다." | ||
const val ERROR_WINNER_NUMBER_INVALID_MESSAGE = "[ERROR] 당첨 번호는 서로 다른 6개의 1부터 45사이의 정수여야 합니다." | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,61 @@ | ||
package lotto | ||
|
||
import camp.nextstep.edu.missionutils.Randoms | ||
|
||
class LottoController { | ||
|
||
val lottos = ArrayList<Lotto>() | ||
|
||
fun startLotto() { | ||
val lottoView = LottoView() | ||
val buyLottoAmount = divideLottoPrice(lottoView.getBuyAmount()) | ||
releaseLotto(buyLottoAmount) | ||
lottoView.showBoughtLotto(buyLottoAmount, lottos) | ||
val winnerNumber = lottoView.getWinnerNumber() | ||
val specialNumber = lottoView.getSpecialNumber(winnerNumber) | ||
val lottoResult = calculateLotto(winnerNumber, specialNumber) | ||
lottoView.showLottoResult(lottoResult) | ||
lottoView.showReturnRate(getReturnRate(lottoResult, buyLottoAmount)) | ||
} | ||
|
||
private fun getReturnRate(lottoResult: List<Int>, buyLottoAmount: Int): Double { | ||
var sum = 0.0 | ||
sum += LottoResult.THREE.prize * lottoResult[0] | ||
sum += LottoResult.FOUR.prize * lottoResult[1] | ||
sum += LottoResult.FIVE.prize * lottoResult[2] | ||
sum += LottoResult.FIVE_SPECIAL.prize * lottoResult[3] | ||
sum += LottoResult.SIX.prize * lottoResult[4] | ||
return sum / (buyLottoAmount * Constant.LOTTO_PRICE) * 100 | ||
} | ||
|
||
private fun calculateLotto(winnerNumber: List<Int>, specialNumber: Int): List<Int> { | ||
val lottoResult = MutableList(5) { 0 } | ||
lottos.forEach { | ||
when (it.getLottoValue().toSet().minus(winnerNumber.toSet()).size) { | ||
3 -> lottoResult[0]++ | ||
2 -> lottoResult[1]++ | ||
1 -> { | ||
if (it.getLottoValue().contains(specialNumber)) lottoResult[3]++ | ||
else lottoResult[2]++ | ||
} | ||
|
||
0 -> lottoResult[4]++ | ||
} | ||
} | ||
return lottoResult | ||
} | ||
|
||
private fun divideLottoPrice(inputBuyAmount: Int): Int { | ||
return inputBuyAmount / Constant.LOTTO_PRICE | ||
} | ||
|
||
private fun releaseLotto(buyLottoAmount: Int) { | ||
repeat(buyLottoAmount) { | ||
lottos.add(Lotto(getLottoNumber())) | ||
} | ||
} | ||
|
||
private fun getLottoNumber(): List<Int> { | ||
return Randoms.pickUniqueNumbersInRange(1, 45, 6).sorted() | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,9 @@ | ||
package lotto | ||
|
||
enum class LottoResult(val prize: Int) { | ||
THREE(5000), | ||
FOUR(50000), | ||
FIVE(1500000), | ||
FIVE_SPECIAL(30000000), | ||
SIX(2000000000) | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,92 @@ | ||
package lotto | ||
|
||
import camp.nextstep.edu.missionutils.Console | ||
|
||
class LottoView { | ||
|
||
fun getBuyAmount(): Int { | ||
println(Constant.INPUT_BUY_MESSAGE) | ||
while (true) { | ||
try { | ||
val buyAmount = Console.readLine() | ||
if (checkBuyAmount(buyAmount)) return buyAmount.toInt() | ||
} catch (e: IllegalArgumentException) { | ||
println(e.message) | ||
} | ||
} | ||
} | ||
|
||
private fun checkBuyAmount(buyAmount: String): Boolean { | ||
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. 뷰 안에서 너무 많은 역할을 담당할 수 있으니, 이렇게 체킹하는 함수는 따로 validator 같은 파일로 분리하는 게 관리하기 좋을 것 같아요!🙂🙂 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. 아하! 확실히 분리하는 게 용도도 명확해보여서 더 좋은 것 같습니다! 감사합니다! |
||
try { | ||
if (buyAmount.toInt() % 1000 == 0) return true | ||
else throw Exception() | ||
} catch (e: Exception) { | ||
throw IllegalArgumentException(Constant.ERROR_BUY_AMOUNT_INVALID_MESSAGE) | ||
} | ||
} | ||
|
||
fun showBoughtLotto(lottoAmount: Int, lottos: ArrayList<Lotto>) { | ||
println("\n$lottoAmount" + Constant.BOUGHT_LOTTO_MESSAGE) | ||
println(lottos.joinToString("\n") { "[" + it.getLottoValue().joinToString(", ") + "]" }) | ||
println() | ||
} | ||
|
||
fun getWinnerNumber(): List<Int> { | ||
while (true) { | ||
try { | ||
println(Constant.INPUT_WINNER_NUMBER) | ||
val winnerNumber = Console.readLine() | ||
if (checkWinnerNumber(winnerNumber)) return winnerNumber.split(",").map { it.toInt() } | ||
} catch (e: IllegalArgumentException) { | ||
println(e.message) | ||
} | ||
} | ||
} | ||
|
||
private fun checkWinnerNumber(winnerNumber: String): Boolean { | ||
try { | ||
if (winnerNumber.split(",").map { if (it.toInt() in 1..45) it.toInt() else throw Exception() } | ||
.toSet().size == 6 | ||
) return true | ||
else throw Exception() | ||
} catch (e: Exception) { | ||
throw IllegalArgumentException(Constant.ERROR_WINNER_NUMBER_INVALID_MESSAGE) | ||
} | ||
} | ||
|
||
fun getSpecialNumber(winnerNumber: List<Int>): Int { | ||
while (true) { | ||
try { | ||
println(Constant.INPUT_SPECIAL_NUMBER_MESSAGE) | ||
val specialNumber = Console.readLine() | ||
if (checkSpecialNumber(specialNumber, winnerNumber)) return specialNumber.toInt() | ||
} catch (e: IllegalArgumentException) { | ||
println(e.message) | ||
} | ||
} | ||
} | ||
|
||
private fun checkSpecialNumber(specialNumber: String, winnerNumber: List<Int>): Boolean { | ||
try { | ||
if (specialNumber.toInt() in 1..45) { | ||
if (!winnerNumber.contains(specialNumber.toInt())) return true | ||
else throw Exception() | ||
} else throw Exception() | ||
} catch (e: Exception) { | ||
throw IllegalArgumentException(Constant.ERROR_SPECIAL_NUMBER_INVALID_MESSAGE) | ||
} | ||
} | ||
|
||
fun showLottoResult(lottoResult: List<Int>) { | ||
println(Constant.RESULT_MESSAGE) | ||
println(Constant.RESULT_THREE_MATCH.format(lottoResult[0])) | ||
println(Constant.RESULT_FOUR_MATCH.format(lottoResult[1])) | ||
println(Constant.RESULT_FIVE_MATCH.format(lottoResult[2])) | ||
println(Constant.RESULT_FIVE_SPECIAL_MATCH.format(lottoResult[3])) | ||
println(Constant.RESULT_SIX_MATCH.format(lottoResult[4])) | ||
} | ||
|
||
fun showReturnRate(returnRate: Double) { | ||
println("총 수익률은 ${String.format("%.1f", returnRate)}%입니다.") | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -19,5 +19,13 @@ class LottoTest { | |
} | ||
} | ||
|
||
@Test | ||
fun `로또 번호가 1에서 45 사이의 숫자가 아니라면 예외가 발생한다`() { | ||
assertThrows<IllegalArgumentException> { | ||
Lotto(listOf(1, 2, 3, 4, 5, 46)) | ||
} | ||
} | ||
|
||
|
||
// TODO: 추가 기능 구현에 따른 테스트 코드 작성 | ||
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. 제가 테스트 코드 작성이 조금 미숙해서... 이번 미션에서 한번 추가해보겠습니다! |
||
} |
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.
로또 가격까지 상수화 하셨군요! 전 이 부분은 생각 못했네요👍