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

Step2-로또(자동) #1063

Open
wants to merge 6 commits into
base: janghomoon
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
26 changes: 26 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,3 +29,29 @@
* 입력값 유효성 검사
* 숫자 이외의 값 또는 음수 전달 시 RunTimeException 예외 발생
* 분리된 숫자 값을 모두 더한다.

----
## step2 로또(자동)

### 프로그램 요구사항
* 모든 기능을 TDD로 구현해 단위 테스트가 존재해야 한다. 단, UI(System.out, System.in) 로직은 제외
* 핵심 로직을 구현하는 코드와 UI를 담당하는 로직을 구분한다.
* UI 로직을 InputView, ResultView와 같은 클래스를 추가해 분리한다.
* indent(인덴트, 들여쓰기) depth를 2를 넘지 않도록 구현한다. 1까지만 허용한다.
* 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
* 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
* 함수(또는 메서드)의 길이가 15라인을 넘어가지 않도록 구현한다.
* 함수(또는 메서드)가 한 가지 일만 잘 하도록 구현한다.
* 기능을 구현하기 전에 README.md 파일에 구현할 기능 목록을 정리해 추가한다.
* git의 commit 단위는 앞 단계에서 README.md 파일에 정리한 기능 목록 단위로 추가한다.


### TODO 리스트
* 입력 부분 구현(금액, 지난 주 당첨 번호)
* 양수 숫자만 입력을 받도록
* 당첨번호는 쉼표로 구분
* 랜덤하게 로또 번호 부여
* 한 회차에 숫자가 중복되지 않도록 값 을 부여
* 당첨 통계
* 회차별 로또 당첨 번호와 일치하는 회차 찾기
* 수익률 계산
22 changes: 22 additions & 0 deletions src/main/kotlin/autolotto/AutoLottoApplication.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package autolotto

import autolotto.configuration.AutoLottoConfiguration
import autolotto.controller.AutoLottoController
import autolotto.view.InputView
import autolotto.view.OutPutView

private val autoLottoController: AutoLottoController =
AutoLottoConfiguration().autoLottoController()

fun main() {
val amount = InputView.getLottoPurchaseAmount()
InputView.printLottoGameCount(amount)

val gameCount = InputView.getLottoGameCount(amount)
val createLottoInfo = autoLottoController.start(gameCount)
OutPutView.printLottoInfo(createLottoInfo)

val winnerNumbers = InputView.getWinningNumber()
val lottoWinnerResult = autoLottoController.getWinnerInfo(winnerNumbers)
OutPutView.printLottoResults(lottoWinnerResult, amount)
}
26 changes: 26 additions & 0 deletions src/main/kotlin/autolotto/calculator/LottoCalculator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package autolotto.calculator

import autolotto.enums.prize.Prize
import kotlin.math.round

object LottoCalculator {

fun getTotalPrize(
lotto: Map<Int, Int>
): Int {
return lotto.map { (number, count) -> getPrize(number, count) }.sum()
}

private fun getPrize(number: Int, count: Int): Int {
val prize = Prize.fromMatchCount(number)
return prize?.calculatePrize(count) ?: 0
}


fun getProfitRate(totalPrize: Int, amount: Int): Double {
return round(
totalPrize /
(totalPrize - amount).toDouble() * 100
) / 100
}
}
19 changes: 19 additions & 0 deletions src/main/kotlin/autolotto/configuration/AutoLottoConfiguration.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package autolotto.configuration

import autolotto.controller.AutoLottoController
import autolotto.repository.LottoRepository
import autolotto.service.LottoService

class AutoLottoConfiguration {
fun autoLottoController(): AutoLottoController {
return AutoLottoController(lottoService())
}

fun lottoService(): LottoService {
return LottoService(LottoRepository())
}

fun lottoRepository(): LottoRepository {
return LottoRepository()
}
janghomoon marked this conversation as resolved.
Show resolved Hide resolved
}
14 changes: 14 additions & 0 deletions src/main/kotlin/autolotto/controller/AutoLottoController.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package autolotto.controller

import autolotto.entity.Lotto
import autolotto.service.LottoService

class AutoLottoController(private val lottoService: LottoService) {
fun start(gameCount: Int): List<Lotto> {
return lottoService.start(gameCount)
}

fun getWinnerInfo(winnersNumbers: List<Int>): Map<Int, Int> {
return lottoService.getWinnerInfo(winnersNumbers)
}
}
4 changes: 4 additions & 0 deletions src/main/kotlin/autolotto/entity/Lotto.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package autolotto.entity

class Lotto(val lottoGame: Set<Int>) {
}
33 changes: 33 additions & 0 deletions src/main/kotlin/autolotto/enums/prize/Prize.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package autolotto.enums.prize

enum class Prize(val matchCount: Int, val prizeMoney: Int) {
THREE(3, 5000) {
override fun calculatePrize(count: Int): Int {
return count * prizeMoney
}
},
FOUR(4, 50000) {
override fun calculatePrize(count: Int): Int {
return (count * prizeMoney) + 1000 // 예: 추가 보너스 계산
}
},
FIVE(5, 100000) {
override fun calculatePrize(count: Int): Int {
return count * prizeMoney / 2 // 예: 50% 지급
}
},
SIX(6, 1000000) {
override fun calculatePrize(count: Int): Int {
return count * prizeMoney
}
};


abstract fun calculatePrize(count: Int): Int

companion object {
fun fromMatchCount(matchCount: Int): Prize? {
return entries.find { it.matchCount == matchCount }
}
}
}
15 changes: 15 additions & 0 deletions src/main/kotlin/autolotto/repository/LottoRepository.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package autolotto.repository

import autolotto.entity.Lotto

class LottoRepository {
private val lottoGames: MutableList<Lotto> = mutableListOf()

fun save(lotto: Lotto) {
this.lottoGames.add(lotto)
}

fun findAll(): List<Lotto> {
return this.lottoGames.toList()
}
}
51 changes: 51 additions & 0 deletions src/main/kotlin/autolotto/service/LottoService.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package autolotto.service

import autolotto.entity.Lotto
import autolotto.repository.LottoRepository

class LottoService(private val lottoRepository: LottoRepository) {
fun start(gameCount: Int): List<Lotto> {
repeat(gameCount) {
lottoRepository.save(Lotto(generateLottoNumbers()))
}
return lottoRepository.findAll()
}

private fun generateLottoNumbers(): Set<Int> {
return (MIN_LOTTO_NUMBER..MAX_LOTTO_NUMBER)
.shuffled()
.take(LOTTO_TAKE_NUMBER)
.toSet()
}

fun getWinnerInfo(winnersNumbers: List<Int>): Map<Int, Int> {
val resultMap = mutableMapOf(
3 to 0,
4 to 0,
5 to 0,
6 to 0
)
val lottos = lottoRepository.findAll()
val comparisonWinningNumbers =
lottos.map { lotto -> comparisonWinningNumbers(lotto, winnersNumbers) }
.groupBy { it }.mapValues { it.value.size }.toMutableMap()
resultMap.forEach { (key, value) ->
resultMap[key] = (comparisonWinningNumbers[key] ?: 0) + value
}
return resultMap
}

private fun comparisonWinningNumbers(
lotto: Lotto,
winnersNumbers: List<Int>,
): Int {
val matchedNumbers = lotto.lottoGame.filter { winnersNumbers.contains(it) }
return matchedNumbers.count()
}

companion object {
private const val MIN_LOTTO_NUMBER = 1
private const val MAX_LOTTO_NUMBER = 45
private const val LOTTO_TAKE_NUMBER = 6
}
}
45 changes: 45 additions & 0 deletions src/main/kotlin/autolotto/valid/Valid.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package autolotto.valid

import kotlin.math.sign

object Valid {
Copy link

Choose a reason for hiding this comment

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

방어 로직만을 위한 util성 정적메서드만 모여있는 클래스 없이

객체 내부에서 검증을 하도록 모두 변경해보시면 좋을것 같습니다.

private const val NEGATIVE_EXCEPTION_MESSAGE = "양수만 입력 가능합니다."
private const val LOTTO_AMOUNT = 1000
private const val REMAINDER_EXCEPTION_MESSAGE = "돈은 1000원 단위만 입력가능합니다."
private const val MIN_AMOUNT_EXCEPTION_MESSAGE = "로또는 최소 1장 이상 구매해야합니다."

fun stringToInt(target: String): Int {
try {
return Integer.parseInt(target)
} catch (nex: NumberFormatException) {
throw NumberFormatException("$target 은 숫자로 변환 하는데 실패했습니다.")
}
}

fun inputNumberValid(target: Int): Int {
validateNegative(target)
return positiveNumber(target)
}

private fun positiveNumber(target: Int): Int {
require(target >= 0) { "$target 은 양수가 아닙니다." }
return target
}

private fun validateNegative(convertedNumber: Int) {
when {
convertedNumber and Int.MIN_VALUE != 0 -> throw RuntimeException(
NEGATIVE_EXCEPTION_MESSAGE,
)

convertedNumber.sign < 0 -> throw RuntimeException(NEGATIVE_EXCEPTION_MESSAGE)
convertedNumber < 0 -> throw RuntimeException(NEGATIVE_EXCEPTION_MESSAGE)
}
}

fun purchaseAmountValid(amount: Int) {
if (amount < LOTTO_AMOUNT) throw RuntimeException(MIN_AMOUNT_EXCEPTION_MESSAGE)
val remainder = amount % LOTTO_AMOUNT
if (remainder != 0) throw RuntimeException(REMAINDER_EXCEPTION_MESSAGE)
}
}
42 changes: 42 additions & 0 deletions src/main/kotlin/autolotto/view/InputView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package autolotto.view

import autolotto.valid.Valid

object InputView {
private val LOTTO_AMOUNT = 1000

fun getLottoGameCount(amount: Int): Int {
return amount / LOTTO_AMOUNT
}

fun printLottoGameCount(amount: Int) {
println("${getLottoGameCount(amount)}개를 구매했습니다.")
}

fun getLottoPurchaseAmount(): Int {
println("구입금액을 입력해 주세요.")
val input: Int = readLine()?.toIntOrNull() ?: throw RuntimeException("0 이 아닌 숫자를 입력해주세요")
val convertValue = Valid.inputNumberValid(input)
Valid.purchaseAmountValid(convertValue)
return convertValue
}

fun getWinningNumber(): List<Int> {
println("지난 주 당첨 번호를 입력해 주세요.")
val input: String? = readLine()
if (input.isNullOrEmpty()) {
throw RuntimeException("당청번호를 입력해주세요.")
}
val splitValue = splitWinningNumbers(input)
return splitValue.map { e -> Valid.inputNumberValid(Valid.run { stringToInt(e) }) }.toList()
}

fun printInputWinningNumber(winningNumbers: List<Int>) {
val winningNumber = winningNumbers.joinToString { "," }
println(winningNumber)
}

private fun splitWinningNumbers(input: String): List<String> {
return input.split(",").map { e -> e.trim() }
}
}
61 changes: 61 additions & 0 deletions src/main/kotlin/autolotto/view/OutPutView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package autolotto.view

import autolotto.calculator.LottoCalculator
import autolotto.entity.Lotto

object OutPutView {
private val FOUR_PRIZE_MONEY = 5000
private val THREE_PRIZE_MONEY = 50000
private val SECOND_PRIZE_MONEY = 1500000
private val FIRST_PRIZE_MONEY = 2000000000

fun printLottoInfo(lottos: List<Lotto>) {
repeat(lottos.size) {
printLotto(lottos[it])
}
}

fun printLotto(lotto: Lotto) {
for (number in lotto.lottoGame) {
print("$number ")
}
println()
}

fun printLottoResults(
lottos: Map<Int, Int>,
amount: Int,
) {
println("당첨 통계")
println("---------")
printLottoResult(lottos)
printLottoProfitRate(lottos, amount)
}

private fun printLottoProfitRate(
lotto: Map<Int, Int>,
amount: Int,
) {
val totalPrize: Int = LottoCalculator.getTotalPrize(lotto)
val profitRate = LottoCalculator.getProfitRate(totalPrize, amount)
println("총 수익률은 ${profitRate}입니다.${if (profitRate < 1) "(기준이 1이기 때문에 결과적으로 손해라는 의미임)" else ""}")
}


private fun printLottoResult(lotto: Map<Int, Int>) {
lotto.forEach() { (number, count) -> printLottoPrizeRank(number, count) }
}

private fun printLottoPrizeRank(
number: Int,
count: Int
) {
when (number) {
3 -> println("${number}개 일치 (${FOUR_PRIZE_MONEY}원) - ${count}개")
4 -> println("${number}개 일치 (${THREE_PRIZE_MONEY}원) - ${count}개")
5 -> println("${number}개 일치 (${SECOND_PRIZE_MONEY}원) - ${count}개")
6 -> println("$number 개 일치 (${FIRST_PRIZE_MONEY}원) - ${count}개")
}
}

}
2 changes: 1 addition & 1 deletion src/main/kotlin/calculator/CalculatorApplication.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ fun main() {
val stringAddCalculator = StringAddCalculator()
val result = stringAddCalculator.add("//;\n1;2;3")
println(result)
}
}
Loading