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] 로또 자동 #797

Open
wants to merge 19 commits into
base: choihwan2
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
7db83e6
기존의 step1 README와 병합
kakao-pavlo-v Jun 19, 2023
bcfc4f5
수정 : 로또의 숫자는 6개이다.
kakao-pavlo-v Jun 21, 2023
070e274
수정 : 로또의 번호는 중복돼서는 안된다.
kakao-pavlo-v Jun 21, 2023
3695ccc
추가 : 로또의 금액은 1000원이다, 로또 숫자는 1이상 45이하이다.
kakao-pavlo-v Jun 21, 2023
1a854bf
파일 분리화
kakao-pavlo-v Jun 21, 2023
5c88c78
추가 : 로또 구입 금액을 입력하면 구입 금액에 해당하는 로또를 발급해야 한다.
kakao-pavlo-v Jun 21, 2023
786c4e6
추가 : 로또 화면 출력을 위한 View 클래스 추가
kakao-pavlo-v Jun 22, 2023
10a286d
추가 : 이전 코드리뷰 수정
kakao-pavlo-v Jul 2, 2023
8504991
추가 : mutable 수정 및 object 클래스 추가
kakao-pavlo-v Jul 2, 2023
a316730
추가 : LotteryGameView object 로 변경
kakao-pavlo-v Jul 3, 2023
092ac13
추가 : LotteryGame, LotteryGameTest 변경
kakao-pavlo-v Jul 3, 2023
9b2f468
추가 : 로또 당첨 횟수 각각 구하기, 수익률 구하기 추가
kakao-pavlo-v Jul 3, 2023
e4875eb
추가 : 실행할수 있는 main 추가
kakao-pavlo-v Jul 3, 2023
656bbb1
추가 : WinningLotteryTest 추가
kakao-pavlo-v Jul 3, 2023
ddfa00c
추가 : LotteryPrizeTest 추가
kakao-pavlo-v Jul 3, 2023
7dc228b
변경 : package 구조 변경
kakao-pavlo-v Jul 4, 2023
a32e800
변경 : LotteryPrize 개수를 enum 이 갖지 않게 변경
kakao-pavlo-v Jul 4, 2023
5d7e88a
변경 : 로또 비교 테스트 추가
kakao-pavlo-v Jul 4, 2023
cec863a
변경 : const val 추가
choihwan2 Jul 5, 2023
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
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,13 @@
# kotlin-lotto

[X]로또 구입 금액을 입력하면 구입 금액에 해당하는 로또를 발급해야 한다.
[X]로또 1장의 가격은 1000원이다.
[X]로또의 숫자는 6개이다.
[X]로또는 당첨 번호는 중복돼서는 안된다.
[X]로또의 수익률을 마지막에 구해주어야한다. (구매금액) / (당첨금액)
[X]로또의 숫자는 1~45까지이다.
[X]로또의 당첨 횟수를 각각 구해야한다.

요구사항
[X]쉼표(,) 또는 콜론(:)을 구분자로 가지는 문자열을 전달하는 경우 구분자를 기준으로 분리한 각 숫자의 합을 반환 (예: “” => 0, "1,2" => 3, "
1,2,3" => 6, “1,2:3” => 6)
Expand Down
40 changes: 40 additions & 0 deletions src/main/kotlin/lottery/controller/LotteryGame.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package lottery.controller

import lottery.domain.Lotteries
import lottery.domain.Lottery
import lottery.domain.LotteryNumber
import lottery.domain.LotteryPrize
import lottery.domain.LotteryRank
import lottery.domain.WinningLottery
import lottery.view.LotteryGameView

class LotteryGame {

Choose a reason for hiding this comment

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

Controller라고 정의하신 LotteryGamepurchaseAutoLotteries 로직이 생긴것 같아요.
LotteryGameLotteryController로 바꾸고 로직을 가지고 있는 LotteryGamedomain 에 만들어볼까요?

private lateinit var winningLottery: WinningLottery

Choose a reason for hiding this comment

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

start()에서 사용할거라서 lateinit과 전역으로 두지 않아도 될것 같네요.

private val lotteryRank = LotteryRank()
fun purchaseAutoLotteries(purchasePrice: Int): Lotteries {
return Lotteries.makeAutoLotteries(purchasePrice / Lottery.LOTTERY_PRICE)
}

fun start() {

Choose a reason for hiding this comment

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

startpurchaseAutoLotteries 를 호출하니, start 밑에 purchaseAutoLotteries 가 있으면 좋겠네요.

LotteryGameView.printPurchaseMoneyView()
val money = readln().toInt()
Comment on lines +19 to +20

Choose a reason for hiding this comment

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

InputView를 만들어서 처리해도 괜찮겠네요~

val purchaseAutoLotteries = purchaseAutoLotteries(money)
LotteryGameView.printPurchaseLotteryView(purchaseAutoLotteries.lotteries.size)
LotteryGameView.printLotteriesNumber(purchaseAutoLotteries)
LotteryGameView.printWinnerLotteryNumber()
val numbers = readln().replace("\\s".toRegex(), "").split(",")
Comment on lines +24 to +25

Choose a reason for hiding this comment

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

이 부분도 InputView라는것을 만들어서 처리하면 될거 같네요. 그러면 다른 funOutputView를 만들어서 처리하면 될것 같아요.

winningLottery = WinningLottery(numbers.map { LotteryNumber.get(it.toInt()) }.toSet())

purchaseAutoLotteries.lotteries.forEach {
val correctCount = it.checkCorrectCount(winningLottery.winningNumbers)
lotteryRank.plusRank(LotteryPrize.get(correctCount) ?: LotteryPrize.NONE)
}
LotteryGameView.printLotteryRankView(lotteryRank)
LotteryGameView.printProfitView(lotteryRank.calculateProfit(money))
}
}

fun main() {

Choose a reason for hiding this comment

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

maincontroller밖의 lottery 패키지에 있어도 되겠네요~

val game = LotteryGame()
game.start()
}
15 changes: 15 additions & 0 deletions src/main/kotlin/lottery/domain/Lotteries.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package lottery.domain

class Lotteries {
val lotteries = mutableListOf<Lottery>()

companion object {
fun makeAutoLotteries(number: Int): Lotteries {
val lotteries = Lotteries()
for (i in 0 until number) {
lotteries.lotteries.add(Lottery.makeAutoLottery())
}
return lotteries
}
}
}
Comment on lines +3 to +15

Choose a reason for hiding this comment

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

객체를 생성할 때 가능하면 완벽한 상태로 생성하는게 좋습니다.

Suggested change
class Lotteries {
val lotteries = mutableListOf<Lottery>()
companion object {
fun makeAutoLotteries(number: Int): Lotteries {
val lotteries = Lotteries()
for (i in 0 until number) {
lotteries.lotteries.add(Lottery.makeAutoLottery())
}
return lotteries
}
}
}
class Lotteries(
val lotteries: List<Lottery>
) {
companion object {
fun makeAutoLotteries(number: Int): Lotteries {
val lotteries = (0..number).map { Lottery.makeAutoLottery() }
return Lotteries(lotteries)
}
}
}

30 changes: 30 additions & 0 deletions src/main/kotlin/lottery/domain/Lottery.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package lottery.domain

class Lottery(numbers: Set<LotteryNumber>) {

Choose a reason for hiding this comment

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

Set 💯

val lotteryNumbers: Set<LotteryNumber>

Choose a reason for hiding this comment

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

생성자에 val을 해도 괜찮겠네요~


init {
require(!hasDuplicatedLotteryNumbers(numbers)) { "로또 번호에 중복되는 숫자가 있습니다." }
lotteryNumbers = numbers
}

private fun hasDuplicatedLotteryNumbers(numbers: Set<LotteryNumber>): Boolean {
return numbers.size != LOTTERY_NUMBER_SIZE
}

fun checkCorrectCount(numbers: Set<LotteryNumber>): Int {
return numbers.count { it in lotteryNumbers }
}

companion object {
private val BASE_NUMBERS = (LotteryNumber.MIN_LOTTERY_NUMBER..LotteryNumber.MAX_LOTTERY_NUMBER).toSet()
const val LOTTERY_NUMBER_SIZE = 6
const val LOTTERY_PRICE = 1000

Choose a reason for hiding this comment

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

가격에 대한 정책은 게임에 더 가까울거 같아요!
만약 Lottery에서 사용하지 않는것이면 해당 객체와 어울리지 않는 상수일 수 있어요.

fun makeAutoLottery(): Lottery {
return Lottery(
BASE_NUMBERS.shuffled().take(LOTTERY_NUMBER_SIZE).sorted().map { LotteryNumber.get(it) }
.toSet()
)
}
}
}
22 changes: 22 additions & 0 deletions src/main/kotlin/lottery/domain/LotteryNumber.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package lottery.domain

@JvmInline
value class LotteryNumber private constructor(
private val number: Int,
) {
init {
require(number >= MIN_LOTTERY_NUMBER) { "로또 번호는 1이상여야 합니다." }
require(number <= MAX_LOTTERY_NUMBER) { "로또 번호는 45이하이여야 합니다." }
}

companion object {
fun get(number: Int) = LotteryNumber(number)

const val MAX_LOTTERY_NUMBER = 45
const val MIN_LOTTERY_NUMBER = 1
}

override fun toString(): String {
return "$number"
}
}
15 changes: 15 additions & 0 deletions src/main/kotlin/lottery/domain/LotteryPrize.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package lottery.domain

enum class LotteryPrize(val correctCount: Int, val rewardMoney: Int) {
NONE(0, 0),
FORTH(3, 5_000),
THIRD(4, 50_000),
SECOND(5, 1_500_000),
FIRST(6, 2_000_000_000), ;

companion object {
fun get(correctCount: Int): LotteryPrize? {
return LotteryPrize.values().find { it.correctCount == correctCount }
}
}
}
20 changes: 20 additions & 0 deletions src/main/kotlin/lottery/domain/LotteryRank.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package lottery.domain

class LotteryRank {

Choose a reason for hiding this comment

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

컨트롤러에서 Lotteries를 반복해서 해당 객체를 만드네요. Lotteries 에 메서드를 추가하여 LotteryRank를 만들게 하는건 어떨까요?

Choose a reason for hiding this comment

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

그러면 팩터리메서드나 생성자를 통해 List<Lottery>를 받을 수 있을것이고, plusRankcalculateProfit 메서드도 사라질 수 있겠네요.

val lotteriesRank = LotteryPrize.values().associateWith { RANK_DEFAULT_VALUE }.toMutableMap()

Choose a reason for hiding this comment

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

이름에서 타입을 알 수 없으면 타입을 명시해볼까요?


fun plusRank(rank: LotteryPrize) {
lotteriesRank[rank] = lotteriesRank.getOrDefault(rank, 0) + 1
}

fun calculateProfit(money: Int): Double {
val total = lotteriesRank.map { (prize, count) ->
prize.rewardMoney * count
}.sumOf { it }.toDouble()
return total / money
}

companion object {
const val RANK_DEFAULT_VALUE = 0
}
}
14 changes: 14 additions & 0 deletions src/main/kotlin/lottery/domain/WinningLottery.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package lottery.domain

class WinningLottery(numbers: Set<LotteryNumber>) {
val winningNumbers: Set<LotteryNumber>
Comment on lines +3 to +4

Choose a reason for hiding this comment

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

이것도 생성자에 val을 붙여서 정의할 수 있겠네요!


init {
require(!hasDuplicatedLotteryNumbers(numbers)) { "로또 번호에 중복되는 숫자가 있습니다." }
winningNumbers = numbers
}

private fun hasDuplicatedLotteryNumbers(numbers: Set<LotteryNumber>): Boolean {
return numbers.size != Lottery.LOTTERY_NUMBER_SIZE
}
}
39 changes: 39 additions & 0 deletions src/main/kotlin/lottery/view/LotteryGameView.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
package lottery.view

import lottery.domain.Lotteries
import lottery.domain.LotteryPrize
import lottery.domain.LotteryRank

object LotteryGameView {

fun printPurchaseMoneyView() {
println("구입 금액을 입력해 주세요.")
}

fun printPurchaseLotteryView(number: Int) {
println("${number}개를 구매했습니다.")
}

fun printLotteriesNumber(lotteries: Lotteries) {
lotteries.lotteries.forEach {
println(it.lotteryNumbers)
}
println()
}

fun printWinnerLotteryNumber() {
println("지난 주 당첨 번호를 입력해 주세요.")
}

fun printLotteryRankView(lotteryRank: LotteryRank) {
println("당첨 통계")
println("---------")
lotteryRank.lotteriesRank.filter { it.key != LotteryPrize.NONE }.forEach { (prize, count) ->
println("${prize.correctCount}개 일치 (${prize.rewardMoney}원)- ${count}개")
}
}

fun printProfitView(profit: Double) {
println("총 수익률은" + String.format("%.2f", profit) + " 입니다.")
}
}
35 changes: 30 additions & 5 deletions src/main/kotlin/stringcalculator/Delimiters.kt
Original file line number Diff line number Diff line change
@@ -1,11 +1,36 @@
package stringcalculator

class Delimiters {
val delimiters = mutableListOf(DEFAULT_DELIMITER_COMMA, DEFAULT_DELIMITER_COLON)
import stringcalculator.Delimiters.Companion.DEFAULT_DELIMITER_COLON
import stringcalculator.Delimiters.Companion.DEFAULT_DELIMITER_COMMA

fun addDelimiter(delimiter: String) {
delimiters.add(delimiter)
object StringParser {
private const val CUSTOM_DELIMITER_INDEX = 1
private const val WITHOUT_CUSTOM_DELIMITER_EXPRESSION_INDEX = 2

fun getDelimitersFromString(text: String): Delimiters {
val matchResult = Regex(Delimiters.CUSTOM_DELIMITER_FIND_REGEX).find(text)
matchResult?.let {
return Delimiters(
listOf(
DEFAULT_DELIMITER_COMMA,
DEFAULT_DELIMITER_COLON,
it.groupValues[CUSTOM_DELIMITER_INDEX]
)
)
}
return Delimiters()
}

fun deleteCustomDelimiters(text: String): String {
val matchResult = Regex(Delimiters.CUSTOM_DELIMITER_FIND_REGEX).find(text)
matchResult?.let {
return it.groupValues[WITHOUT_CUSTOM_DELIMITER_EXPRESSION_INDEX]
}
return text
}
}

class Delimiters(val delimiters: List<String> = listOf(DEFAULT_DELIMITER_COMMA, DEFAULT_DELIMITER_COLON)) {

fun getDelimitersRegex(): Regex {
return delimiters.joinToString("", "[", "]").toRegex()
Expand All @@ -14,6 +39,6 @@ class Delimiters {
companion object {
const val DEFAULT_DELIMITER_COMMA = ","
const val DEFAULT_DELIMITER_COLON = ":"
const val CUSTOM_DELIMITER_FIND_REGEX = "//(.)\n(.*)"
const val CUSTOM_DELIMITER_FIND_REGEX = "//(.*)\n(.*)"
}
}
7 changes: 5 additions & 2 deletions src/main/kotlin/stringcalculator/StringAddCalculator.kt
Original file line number Diff line number Diff line change
@@ -1,10 +1,13 @@
package stringcalculator

class StringAddCalculator {
private val delimiters = Delimiters()
private lateinit var delimiters: Delimiters

fun calculate(text: String?): Int {
if (text.isNullOrEmpty()) return 0
val parseText = StringParser.deleteCustomDelimiters(text, delimiters)
delimiters = StringParser.getDelimitersFromString(text)
val parseText = StringParser.deleteCustomDelimiters(text)

return parseText.split(delimiters.getDelimitersRegex()).sumOf {
if (it.toInt() < 0) throw RuntimeException()
it.toInt()
Expand Down
16 changes: 0 additions & 16 deletions src/main/kotlin/stringcalculator/StringParser.kt

This file was deleted.

12 changes: 12 additions & 0 deletions src/test/kotlin/lottery/controller/LotteryGameTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package lottery.controller

import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe

class LotteryGameTest : StringSpec({
"로또 구입 금액을 입력하면 구입 금액에 해당하는 로또를 발급해야 한다." {
val lotteryGame = LotteryGame()
val purchaseLotteries = lotteryGame.purchaseAutoLotteries(14000)
purchaseLotteries.lotteries.size shouldBe 14
}
})
18 changes: 18 additions & 0 deletions src/test/kotlin/lottery/domain/LotteryNumberTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package lottery.domain

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.StringSpec

class LotteryNumberTest : StringSpec({
"로또 숫자의 크기는 1이상 이여야한다." {
shouldThrow<IllegalArgumentException> {
LotteryNumber.get(0)
}
}

"로또 숫자의 크기는 45이하여야 한다." {
shouldThrow<IllegalArgumentException> {
LotteryNumber.get(46)
}
}
})
13 changes: 13 additions & 0 deletions src/test/kotlin/lottery/domain/LotteryPrizeTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package lottery.domain

import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe

class LotteryPrizeTest : StringSpec({
"correctCount 갯수에 해당하는 LotteryPrize 를 가져와야한다." {
LotteryPrize.get(correctCount = 3) shouldBe LotteryPrize.FORTH
LotteryPrize.get(correctCount = 4) shouldBe LotteryPrize.THIRD
LotteryPrize.get(correctCount = 5) shouldBe LotteryPrize.SECOND
LotteryPrize.get(correctCount = 6) shouldBe LotteryPrize.FIRST
}
})
20 changes: 20 additions & 0 deletions src/test/kotlin/lottery/domain/LotteryRankTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package lottery.domain

import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe

class LotteryRankTest : StringSpec({
"plusRank 호출시 해당하는 로또 등수를 올려주어야한다." {
val lotteryRank = LotteryRank()
lotteryRank.lotteriesRank[LotteryPrize.THIRD] shouldBe 0
lotteryRank.plusRank(LotteryPrize.THIRD)
lotteryRank.lotteriesRank[LotteryPrize.THIRD] shouldBe 1
}

"로또 구매 금액과 수익금으로 수익률(금액 / 구매금액)을 계산해 주어야한다." {
val lotteryRank = LotteryRank()
lotteryRank.calculateProfit(1000) shouldBe 0
lotteryRank.plusRank(LotteryPrize.FORTH)
lotteryRank.calculateProfit(1000) shouldBe 5
}
})
31 changes: 31 additions & 0 deletions src/test/kotlin/lottery/domain/LotteryTest.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package lottery.domain

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.core.spec.style.StringSpec
import io.kotest.matchers.shouldBe

class LotteryTest : StringSpec({
"자동 로또 생성을 할 경우의 숫자의 개수는 6개이다." {
val lottery = Lottery.makeAutoLottery()
lottery.lotteryNumbers.size shouldBe 6
}

"로또는 번호는 중복돼서는 안된다." {
val duplicatedNumbers = listOf(1, 2, 3, 1, 4, 5)
shouldThrow<IllegalArgumentException> {
Lottery(duplicatedNumbers.map { LotteryNumber.get(it) }.toSet())
}
}

"로또 한장의 금액은 1000원 이다." {
Lottery.LOTTERY_PRICE shouldBe 1000
}

"로또 두개를 비교했을때 맞는 개수를 정확하게 가져와야한다." {
val numbers = listOf(1, 2, 3, 4, 5, 6)
val winningNumbers = listOf(1, 2, 3, 8, 9, 10)
val lottery = Lottery(numbers.map { LotteryNumber.get(it) }.toSet())
val winningLottery = WinningLottery(winningNumbers.map { LotteryNumber.get(it) }.toSet())
lottery.checkCorrectCount(winningLottery.winningNumbers) shouldBe 3
}
})
Loading