Skip to content
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,15 @@
로또 (Data)
- [O] 로또 번호의 조합을 가지고 있는다.

당첨 로또
- [O] 당첨 번호 일치 개수 반환
- [O] 보너스 번호 일치 검증
- [] 당첨번호 & 보너스 번호 중복

로또 계산기
- [O] 수익률 반환
- [O] 게임 횟수 반환

로또 머신 (object)
- [O] 입력받은 번호로 구성된 로또로 생성한다.
- [O] 당첨 번호 생성(with 보너스 번호)
Expand Down
1 change: 0 additions & 1 deletion src/main/kotlin/lotto/LottoMain.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package lotto

import lotto.data.LottoNumber
import lotto.domain.LottoMachine
import lotto.domain.RandomLogic
import lotto.service.LottoGame
Expand Down
54 changes: 26 additions & 28 deletions src/main/kotlin/lotto/data/LottoRanking.kt
Original file line number Diff line number Diff line change
@@ -1,36 +1,34 @@
package lotto.data

enum class LottoRanking(val matchingNumberCnt: Int, val price: Int) : Prize {
FirstPlace(6, 2_000_000_000) {
override fun findPrize(winningStatus: Pair<LottoRanking, Int>): Int {
return winningStatus.first.price * winningStatus.second
}
},
SecondPlace(5, 30_000_000) {
override fun findPrize(winningStatus: Pair<LottoRanking, Int>): Int {
return winningStatus.first.price * winningStatus.second
}
},
ThirdPlace(5, 1_500_000) {
override fun findPrize(winningStatus: Pair<LottoRanking, Int>): Int {
return winningStatus.first.price * winningStatus.second
}
},
FourthPlace(4, 50_000) {
override fun findPrize(winningStatus: Pair<LottoRanking, Int>): Int {
return winningStatus.first.price * winningStatus.second
}
},
FifthPlace(3, 5_000) {
override fun findPrize(winningStatus: Pair<LottoRanking, Int>): Int {
return winningStatus.first.price * winningStatus.second
FirstPlace(6, 2_000_000_000),
SecondPlace(5, 30_000_000),
ThirdPlace(5, 1_500_000),
FourthPlace(4, 50_000),
FifthPlace(3, 5_000),
None(0, 0);

override fun findPrize(winningStatus: Pair<LottoRanking, Int>): Int {
return winningStatus.first.price * winningStatus.second
}

companion object {
fun findLottoRanking(matchingNumberCnt: Int, hasBonusNumber: Boolean): LottoRanking {
return if (matchingNumberCnt == SecondPlace.matchingNumberCnt) {
determineRankingWithBonusNumber(hasBonusNumber)
} else {
LottoRanking.values().find { it.matchingNumberCnt == matchingNumberCnt } ?: None
}
Comment on lines +16 to +21
Copy link

Choose a reason for hiding this comment

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

정적 팩토리 함수에서 직접 2등인지 보너스 번호를 비교해야할까요? 이런 특별한 플래그들에 대한 조건들이 보너스 넘버가 끝이 아니라 계속 해서 추가되면 매번 이 함수에 조건들이 추가되야 할 것 같은데, 괜찮을까요?

}
},
None(0, 0) {
override fun findPrize(winningStatus: Pair<LottoRanking, Int>): Int {
return winningStatus.first.price * winningStatus.second

private fun determineRankingWithBonusNumber(isContainBonusNumber: Boolean): LottoRanking {
return if (isContainBonusNumber) {
SecondPlace
} else {
ThirdPlace
}
}
};
}
}

fun interface Prize {
Expand Down
23 changes: 22 additions & 1 deletion src/main/kotlin/lotto/data/WinningLotto.kt
Original file line number Diff line number Diff line change
@@ -1,3 +1,24 @@
package lotto.data

data class WinningLotto(val lotto: Lotto, val bonusNumber: LottoNumber)
data class WinningLotto(val lotto: Lotto, val bonusNumber: LottoNumber) {

init {
validateWinningLotto()
}

fun countMatchingNumbers(lotto: Lotto): Int {
return this.lotto.selectNumbers.intersect(lotto.selectNumbers).size
Copy link

Choose a reason for hiding this comment

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

모든 로직을 작성할때 dot(.)이 몇 개인지 체크해보는것도 좋을 것 같아요. 개인적으로는 Stream API와 같이 중간 오퍼레이터들이 있는 지연 연산되는 특수한 경우를 제외하고는 2개 이상의 dot(.)이 있는경우 내가 지금 이 객체의 속살을 모두 꺼내서 요리를 하는게 아닌가? 고민해볼 수 있어요.

Suggested change
return this.lotto.selectNumbers.intersect(lotto.selectNumbers).size
return this.lotto.matchCount(targetLotto)

}

fun hasBonusNumber(lotto: Lotto): Boolean {
return lotto.selectNumbers.contains(bonusNumber)
}

private fun validateWinningLotto() {
validateDuplicationBonusNumber()
}

private fun validateDuplicationBonusNumber() {
require(!lotto.selectNumbers.contains(bonusNumber)) { "당첨 번호 구성과 보너스 번호가 중복됩니다." }
}
}
18 changes: 18 additions & 0 deletions src/main/kotlin/lotto/domain/LottoCalculator.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package lotto.domain

import lotto.data.LottoRanking

object LottoCalculator {

private const val GAME_COST = 1000
Copy link

Choose a reason for hiding this comment

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

보통 상품의 가격은 상품에서 가지고 있죠.
로또의 가격은 로또 계산기에 있는게 맞을까요 로또가 가지고 있는게 맞을까요 ?!


fun calculateWinningRate(cash: Int, winningStatus: Map<LottoRanking, Int>): Float {
Copy link

Choose a reason for hiding this comment

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

map자료구조에서 열거타입을 사용한다면 EnumMap을 고려해보면 좋을 것 같아요.

val totalPrice = winningStatus.toList().sumOf { it.first.findPrize(it) }

return totalPrice / cash.toFloat()
}

fun getTimes(cash: Int): Int {
return cash / GAME_COST
}
}
35 changes: 4 additions & 31 deletions src/main/kotlin/lotto/domain/LottoMachine.kt
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,6 @@ package lotto.domain
import lotto.data.Lotto
import lotto.data.LottoNumber
import lotto.data.LottoRanking
import lotto.data.LottoRanking.FifthPlace
import lotto.data.LottoRanking.FirstPlace
import lotto.data.LottoRanking.FourthPlace
import lotto.data.LottoRanking.None
import lotto.data.LottoRanking.SecondPlace
import lotto.data.LottoRanking.ThirdPlace
import lotto.data.WinningLotto

object LottoMachine {
Expand All @@ -24,34 +18,13 @@ object LottoMachine {
}

fun checkLotto(purchaseLotto: Lotto, winningLotto: WinningLotto): LottoRanking {
val intersectNumber = winningLotto.lotto.selectNumbers.intersect(purchaseLotto.selectNumbers)
val matchingNumberCnt = winningLotto.countMatchingNumbers(purchaseLotto)
val hasBonusNumber = winningLotto.hasBonusNumber(purchaseLotto)

return when (intersectNumber.size) {
FirstPlace.matchingNumberCnt -> FirstPlace
SecondPlace.matchingNumberCnt, ThirdPlace.matchingNumberCnt -> {
checkSecondPlace(purchaseLotto, winningLotto.bonusNumber)
}
FourthPlace.matchingNumberCnt -> FourthPlace
FifthPlace.matchingNumberCnt -> FifthPlace
else -> None
}
return LottoRanking.findLottoRanking(matchingNumberCnt, hasBonusNumber)
}

fun createWinningRate(cash: Int, winningStatus: Map<LottoRanking, Int>): Float {
val totalPrice = createTotalWinningPrice(winningStatus)

return totalPrice / cash.toFloat()
}

private fun checkSecondPlace(purchaseLotto: Lotto, bonusLottoNumber: LottoNumber): LottoRanking {
return if (purchaseLotto.selectNumbers.contains(bonusLottoNumber)) {
SecondPlace
} else {
ThirdPlace
}
}

private fun createTotalWinningPrice(winningStatus: Map<LottoRanking, Int>): Int {
return winningStatus.toList().sumOf { it.first.findPrize(it) }
return LottoCalculator.calculateWinningRate(cash, winningStatus)
}
}
14 changes: 7 additions & 7 deletions src/main/kotlin/lotto/service/LottoGame.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,30 +4,30 @@ import lotto.data.Lotto
import lotto.data.LottoNumber
import lotto.data.LottoRanking
import lotto.data.WinningLotto
import lotto.domain.LottoCalculator
import lotto.domain.LottoMachine
import lotto.domain.RandomLogicInterface
import lotto.domain.WinningDomain

class LottoGame(private val randomLogic: RandomLogicInterface) {

fun buyLotto(cash: Int): List<Lotto> {
val lottoList = mutableListOf<Lotto>()
val times = cash / GAME_COST
val times = LottoCalculator.getTimes(cash)

return createLotto(times)
}

private fun createLotto(times: Int): List<Lotto> {
val lottoList = mutableListOf<Lotto>()
repeat(times) {
val lottoNumberCombination = LottoNumber.createRandomLottoNumber(randomLogic)
lottoList.add(LottoMachine.createSelectLotto(lottoNumberCombination))
}

return lottoList
}

fun getWinningStats(winningLotto: WinningLotto, purchaseLottoList: List<Lotto>): Map<LottoRanking, Int> {

return WinningDomain.checkWinningResult(winningLotto, purchaseLottoList)
}

companion object {
private const val GAME_COST = 1000
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package lotto
package lotto.data

import lotto.data.LottoNumber
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package lotto
package lotto.data

import lotto.data.Lotto
import lotto.data.LottoNumber
import org.assertj.core.api.Assertions
import org.junit.jupiter.api.Test

Expand Down
58 changes: 58 additions & 0 deletions src/test/kotlin/lotto/data/WinningLottoTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
package lotto.data

import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test

class WinningLottoTest {

@Test
fun `당첨 번호와 일치하는 번호 수 반환`() {
// given : 당첨로또와 로또를 받는다.
Copy link

Choose a reason for hiding this comment

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

주석에 행위에 대한 설명을 작성하는건 외부에 의한 요인으로 인한 변경점이 있어서 함수 시그니처로 나타내기 부족한 경우처럼 필수적인 경우가 아니라면 비권장드립니다.

컴파일시 체크할 수 있는 항목도 아니기에 기능변경이 일어나거나 관련 시그니처가 변경될때 해당 개발자가 직접 체크하지 않는한 놓치게 될 수 있고, 이로인해 시그니처와 주석이 달라지게되면서 차후 새로운 개발자는 혼동을 겪을 수 있습니다. 즉 가독성이 떨어지게 되는 것이죠.

val lotto1 = Lotto(LottoNumber.createLottoNumbers(listOf(1, 2, 3, 4, 5, 6)))
val lotto2 = Lotto(LottoNumber.createLottoNumbers(listOf(1, 2, 3, 4, 5, 7)))

// 당첨번호 [1,2,3,4,5,7] + 보너스 번호 [6]
val wLotto1 = Lotto(LottoNumber.createLottoNumbers(listOf(1, 2, 3, 4, 5, 7)))
val winningLotto = WinningLotto(wLotto1, LottoNumber.from(6))

// when : 검증 로직을 실행한다.
val actual1 = winningLotto.countMatchingNumbers(lotto1)
val actual2 = winningLotto.countMatchingNumbers(lotto2)

// then : 일치하는 번호의 개수를 반환한다.
assertThat(actual1).isEqualTo(5)
assertThat(actual2).isEqualTo(6)
}

@Test
fun `보너스 번호 일치 검증`() {
// given : 당첨로또와 로또를 받는다.
val lotto1 = Lotto(LottoNumber.createLottoNumbers(listOf(1, 2, 3, 4, 5, 6)))
val lotto2 = Lotto(LottoNumber.createLottoNumbers(listOf(1, 2, 3, 4, 5, 7)))

// 당첨번호 [1,2,3,4,5,7] + 보너스 번호 [6]
val wLotto1 = Lotto(LottoNumber.createLottoNumbers(listOf(1, 2, 3, 4, 5, 7)))
val winningLotto = WinningLotto(wLotto1, LottoNumber.from(6))

// when : 보너스 번호 일치 검증 로직을 호출한다.
val actual1 = winningLotto.hasBonusNumber(lotto1)
val actual2 = winningLotto.hasBonusNumber(lotto2)

// then :
assertThat(actual1).isTrue()
assertThat(actual2).isFalse()
}

@Test
fun `보너스 번호 중복 검증`() {
// given : 당첨 번호로 구성된 로또와 이와 중복되는 보너스 번호를 받는다.
val lotto = Lotto(LottoNumber.createLottoNumbers(listOf(1, 2, 3, 4, 5, 6)))
val bonusLottoNumber = LottoNumber.from(6)

// when :
val actual = runCatching { WinningLotto(lotto, bonusLottoNumber) }.exceptionOrNull()

// then :
assertThat(actual).isInstanceOf(IllegalArgumentException::class.java)
}
}
22 changes: 22 additions & 0 deletions src/test/kotlin/lotto/domain/LottoCalculatorTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package lotto.domain

import lotto.data.LottoRanking
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test

class LottoCalculatorTest {
@Test
fun `로또 수익율 반환 로직`() {
// given : 구매한 로또의 통계와 구매 금액을 받는다.
// 총 당첨금 5천원
val cash = 100000
val winningStatus = mutableMapOf<LottoRanking, Int>()
winningStatus[LottoRanking.FifthPlace] = 1

// when : 구매 금액 대비 당첨 금액에 대한 수익률은 요청한다.
val actual: Float = LottoCalculator.calculateWinningRate(cash, winningStatus)

// then : 수익률이 반환된다.
assertThat(actual).isEqualTo(0.05f)
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package lotto
package lotto.domain

import lotto.data.Lotto
import lotto.data.LottoNumber
import lotto.data.LottoRanking
import lotto.data.WinningLotto
import lotto.domain.LottoMachine
import org.assertj.core.api.Assertions.assertThat
import org.junit.jupiter.api.Test

Expand Down