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

Step4 수동 로또 #1134

Open
wants to merge 28 commits into
base: goodbyeyo
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
99369f0
feat: Lotto 도메인 테스트 및 구현
goodbyeyo Dec 3, 2024
005d6e4
feat: 로또 번호, 티켓 발급 기능 구현 및 테스트
goodbyeyo Dec 7, 2024
e7da511
refactor: Test 코드 내 도메인 로직 Class 분리 리팩토링
goodbyeyo Dec 9, 2024
bce5ce9
feat: 당첨 로또 기능 구현
goodbyeyo Dec 9, 2024
89f9d1c
feat: 로또 컨트롤러, 입출력 뷰 구현
goodbyeyo Dec 11, 2024
d978556
refactor: 피드백 반영, LottoTickets.calculateLottoRank 테스트 추가
goodbyeyo Dec 11, 2024
847f352
refactor: 피드백 반영
goodbyeyo Dec 13, 2024
86bc2a4
Step3 로또 2등 (#1131)
goodbyeyo Dec 17, 2024
86f6958
Step3 로또 2등 (#1131)
goodbyeyo Dec 17, 2024
caac3eb
refactor: 피드백 반영, LottoTickets.calculateLottoRank 테스트 추가
goodbyeyo Dec 11, 2024
6f6d991
fix: upstream rebase 후 발생한 테스코 코드 오류 수정
goodbyeyo Dec 18, 2024
57f7cea
refactor: 피드백 반영
goodbyeyo Dec 18, 2024
b0d50ff
refactor: 피드백 반영
goodbyeyo Dec 18, 2024
2eedaa5
feat: Step4 수동 로또 기능 구현
goodbyeyo Dec 18, 2024
74cdcd6
feat: Lotto 도메인 테스트 및 구현
goodbyeyo Dec 3, 2024
262e88d
feat: 로또 번호, 티켓 발급 기능 구현 및 테스트
goodbyeyo Dec 7, 2024
e9abd55
refactor: Test 코드 내 도메인 로직 Class 분리 리팩토링
goodbyeyo Dec 9, 2024
6677340
feat: 당첨 로또 기능 구현
goodbyeyo Dec 9, 2024
e2fddbb
feat: 로또 컨트롤러, 입출력 뷰 구현
goodbyeyo Dec 11, 2024
a115791
refactor: 피드백 반영, LottoTickets.calculateLottoRank 테스트 추가
goodbyeyo Dec 11, 2024
cfee66b
Step3 로또 2등 (#1131)
goodbyeyo Dec 17, 2024
c6f4996
refactor: 피드백 반영, LottoTickets.calculateLottoRank 테스트 추가
goodbyeyo Dec 11, 2024
3e9cf86
fix: upstream rebase 후 발생한 테스코 코드 오류 수정
goodbyeyo Dec 18, 2024
84a5ddf
Merge branch 'goodbyeyo' into feature/step4-lotto-manual
goodbyeyo Dec 19, 2024
07bd5fb
refactor : 피드백 반영
goodbyeyo Dec 21, 2024
08c5320
refactor : 피드백 반영
goodbyeyo Dec 21, 2024
69535ed
refactor : 피드백 반영
goodbyeyo Dec 21, 2024
82770fe
refactor : 피드백 반영
goodbyeyo Dec 21, 2024
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
57 changes: 46 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -108,17 +108,52 @@

##### `요구사항 정리`
- 보너스 볼
- [ ] 보너스 볼을 입력 받을수 있다
- [ ] 보너스 볼은 당첨 로또 티켓의 6개 번호와 중복 될 수 없다
- [x] 보너스 볼을 입력 받을수 있다
- [x] 보너스 볼은 당첨 로또 티켓의 6개 번호와 중복 될 수 없다
- 당첨 로또
- [ ] 당첨 로또는 1개의 로또 티켓과 1개의 보너스 번호를 가진다
- [ ] 로또 랭크 계산 역할을 담당한다
- [x] 당첨 로또는 1개의 로또 티켓과 1개의 보너스 번호를 가진다
- [x] 로또 랭크 계산 역할을 담당한다
- 로또 티켓
- [ ] 로또 랭크 계산 역할을 당첨 로또에게 위임한다
- [x] 로또 랭크 계산 역할을 당첨 로또에게 위임한다
- 로또 랭크
- [ ] 당첨번호와 5개 일치하고 보너스 번호도 같으면 2등이다,
- [ ] 당첨번호와 5개 일치하고 보너스 번호가 다르면 3등이다
- [ ] 로또 2등의 당첨금액은 1_500_000원 -> 30_000_000원
- [ ] 로또 3등의 당첨금액은 50_000원 -> 1_500_000원
- [ ] 로또 4등의 당첨금액은 5_000원 -> 50_000원
- [ ] 로또 5등의 당첨금액은 5_000원
- [x] 당첨번호와 5개 일치하고 보너스 번호도 같으면 2등이다,
- [x] 당첨번호와 5개 일치하고 보너스 번호가 다르면 3등이다
- [x] 로또 2등의 당첨금액은 1_500_000원 -> 30_000_000원
- [x] 로또 3등의 당첨금액은 50_000원 -> 1_500_000원
- [x] 로또 4등의 당첨금액은 5_000원 -> 50_000원
- [x] 로또 5등의 당첨금액은 5_000원

---

### STEP4 로또 수동

##### `기능 요구사항`
- 현재 로또 생성기는 자동 생성 기능만 제공한다.
- 사용자가 수동으로 추첨 번호를 입력할 수 있도록 해야 한다.
- 입력한 금액, 자동 생성 숫자, 수동 생성 번호를 입력하도록 해야 한다.

##### `실행 결과`
```text
구입금액을 입력해 주세요.
14000

---------추가부분--------------
수동으로 구매할 로또 수를 입력해 주세요.
3

수동으로 구매할 번호를 입력해 주세요.
8, 21, 23, 41, 42, 43
3, 5, 11, 16, 32, 38
7, 11, 16, 35, 36, 44

수동으로 3장, 자동으로 11개를 구매했습니다.
--------------------------------
[8, 21, 23, 41, 42, 43]
지난 주 당첨 번호를 입력해 주세요.
1, 2, 3, 4, 5, 6
보너스 볼을 입력해 주세요.
7

당첨 통계
[... 생략 ...]
```
9 changes: 1 addition & 8 deletions src/main/kotlin/lotto/LottoApplication.kt
Original file line number Diff line number Diff line change
@@ -1,14 +1,7 @@
package lotto

import lotto.controller.LottoController
import lotto.view.InputView
import lotto.view.OutputView

fun main() {
val amount = InputView.getUserAmount()
val lottoTickets = LottoController.purchaseLotto(amount)
OutputView.printPurchaseResult(lottoTickets)
val winningLotto = InputView.getUserWinningLotto()
val lottoResults = LottoController.calculateLottoRank(lottoTickets, winningLotto)
OutputView.printResults(lottoResults, amount)
LottoController.startLottoGame()
}
21 changes: 10 additions & 11 deletions src/main/kotlin/lotto/controller/LottoController.kt
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
package lotto.controller

import lotto.domain.LottoResults
import lotto.domain.LottoTickets
import lotto.domain.WinningLotto
import lotto.view.InputView
import lotto.view.OutputView

object LottoController {
fun purchaseLotto(amount: Int): LottoTickets {
return LottoTickets.purchase(amount)
}

fun calculateLottoRank(
lottoTickets: LottoTickets,
winningLotto: WinningLotto,
): LottoResults {
return lottoTickets.calculateLottoRank(winningLotto)
fun startLottoGame() {
val purchasedDetail = InputView.getPurchaseDetail()
val autoLottoTickets = LottoTickets.generateAutoLottoTickets(purchasedDetail.autoLottoQuantity)
val lottoTickets = LottoTickets(purchasedDetail.manualLottoTickets + autoLottoTickets)
OutputView.printPurchaseResult(lottoTickets, purchasedDetail)
val winningLotto = InputView.getUserWinningLotto()
val lottoResults = lottoTickets.calculateLottoRank(winningLotto)
OutputView.printResults(lottoResults, purchasedDetail.purchaseAmount)
}
}
4 changes: 3 additions & 1 deletion src/main/kotlin/lotto/domain/LottoNumber.kt
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package lotto.domain

data class LottoNumber(val number: Int) {
data class LottoNumber(private val number: Int) {
fun getNumber() = number

companion object {
const val LOTTO_MIN_NUMBER = 1
const val LOTTO_MAX_NUMBER = 45
Expand Down
8 changes: 8 additions & 0 deletions src/main/kotlin/lotto/domain/LottoTicket.kt
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ data class LottoTicket(private val numbers: Set<LottoNumber>) : Collection<Lotto

companion object {
const val LOTTO_TICKET_SIZE = 6
private const val DELIMITER = ","

fun generateLottoNumber(): LottoTicket {
val lottoNumbers =
Expand All @@ -29,6 +30,13 @@ data class LottoTicket(private val numbers: Set<LottoNumber>) : Collection<Lotto
return LottoTicket(lottoNumbers)
}

fun makeLottoTicket(line: String): LottoTicket {
val numbers =
line.split(DELIMITER)
.map { it.trim().toIntOrNull() ?: throw IllegalArgumentException("로또 번호는 숫자여야 합니다.") }
return from(numbers.toSet())
}

fun from(numbers: Set<Int>): LottoTicket {
return LottoTicket(numbers.map { LottoNumber(it) }.toSet())
}
Expand Down
10 changes: 9 additions & 1 deletion src/main/kotlin/lotto/domain/LottoTickets.kt
Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,21 @@ class LottoTickets(private val lottoTickets: List<LottoTicket>) : Collection<Lot
}

companion object {
private const val LOTTO_TICKET_PRICE = 1000
const val LOTTO_TICKET_PRICE = 1000

fun purchase(amount: Int): LottoTickets {
Copy link

Choose a reason for hiding this comment

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

해당 함수는 이제 미사용으로 보이네요! :)

require(amount >= LOTTO_TICKET_PRICE) { "구입금액은 ${LOTTO_TICKET_PRICE}원 이상이여야 합니다" }
val quantity = amount / LOTTO_TICKET_PRICE
val lottoTickets = List(quantity) { LottoTicket.generateLottoNumber() }
return LottoTickets(lottoTickets)
}

fun generateAutoLottoTickets(autoLottoQuantity: Int): LottoTickets {
if (autoLottoQuantity > 0) {
val lottoTickets = List(autoLottoQuantity) { LottoTicket.generateLottoNumber() }
return LottoTickets(lottoTickets)
}
return LottoTickets(listOf())
}
}
}
5 changes: 5 additions & 0 deletions src/main/kotlin/lotto/domain/Money.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
package lotto.domain

class Money(money: String) {
val amount: Int = money.toIntOrNull() ?: throw IllegalArgumentException("구입 금액이 유효하지 않습니다. 숫자를 입력해주세요")
}
40 changes: 40 additions & 0 deletions src/main/kotlin/lotto/dto/PurchaseDetail.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package lotto.dto

import lotto.domain.LottoTickets
import lotto.domain.LottoTickets.Companion.LOTTO_TICKET_PRICE
import lotto.domain.Money

data class PurchaseDetail(
Copy link

Choose a reason for hiding this comment

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

그리고 DTO 는 메서드로 접근하지 않고 바로 필드에 접근해도 괜찮은지 선호님의 생각이 궁금합니다.

getPurchaseAmount가 아닌 purchaseAmount를 바로 호출하는 것과 관련해서 말씀주시는게 맞을까요??

https://velog.io/@paulus0617/kotlin-getter-setter 요글을 읽어보시면 프로퍼티는 필드(Field)와 접근자 메서드(getter, setter)를 하나로 합친 것을 의미합니다. 라고 되어있는데요!

그래서 코틀린 진영에서는 필드가 아닌 프로퍼티라는 네이밍으로 사용하고 있습니다 :)

디컴파일 해보시면 실제 javaCode에서는 getPurchaseAmount 를 통해 접근하시는걸 확인하실 수 있을거에요!

혹시 제가 질문을 잘 못이해한거라면 다시한번 말씀부탁드림니다!

val purchaseAmount: Int,
Copy link

Choose a reason for hiding this comment

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

https://edu.nextstep.camp/s/l3Ppxbm8/ls/ROS71xiT 에 보면 모든 원시 값과 문자열을 포장한다. 라고 되어있는데요!! purchaseAmout도 Money와 같은 객체로 포장해볼 수 있지 않을까요??

val autoLottoQuantity: Int,
val manualLottoQuantity: Int,
val manualLottoTickets: LottoTickets,
) {
init {
require(manualLottoQuantity * LOTTO_TICKET_PRICE <= purchaseAmount) { "수동으로 구입할 로또의 수량이 구입금액보다 많습니다" }
require(autoLottoQuantity <= purchaseAmount / LOTTO_TICKET_PRICE) { "자동으로 구입할 로또의 수량이 구입금액보다 많습니다" }
}
constructor(
money: Money,
manualLottoTickets: LottoTickets,
) : this(
money.amount,
money.amount / LOTTO_TICKET_PRICE - manualLottoTickets.size,
manualLottoTickets.size,
manualLottoTickets,
)

companion object {
fun of(
money: Money,
manualLottoTickets: LottoTickets,
): PurchaseDetail {
return PurchaseDetail(
money.amount,
money.amount / LOTTO_TICKET_PRICE - manualLottoTickets.size,
manualLottoTickets.size,
manualLottoTickets,
)
}
}
}
34 changes: 31 additions & 3 deletions src/main/kotlin/lotto/view/InputView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,19 +2,47 @@ package lotto.view

import lotto.domain.LottoNumber
import lotto.domain.LottoTicket
import lotto.domain.LottoTickets
import lotto.domain.Money
import lotto.domain.WinningLotto
import lotto.dto.PurchaseDetail

object InputView {
fun getUserAmount(): Int {
private const val DELIMITER = ","

fun getPurchaseDetail(): PurchaseDetail {
val money = getPurchaseAmount()
val manualLottoQuantity = getManualLottoQuantity()
val manualLottoTickets = getManualLottoTickets(manualLottoQuantity)
return PurchaseDetail.of(money, manualLottoTickets)
}

private fun getManualLottoTickets(manualLottoQuantity: Int): LottoTickets {
Copy link

Choose a reason for hiding this comment

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

LottoTicket을 만드는 책임이 View에 있는 것 같아요! 자동로또와 동일�한 레벨로 옮겨보면 어떨까요?

println("수동으로 구매할 번호를 입력해 주세요.")
val manualLottoTickets =
List(manualLottoQuantity) {
val readLine = readlnOrNull()?.takeIf { it.isNotBlank() } ?: throw IllegalArgumentException("로또 번호를 입력해주세요.")
LottoTicket.makeLottoTicket(readLine)
}
return LottoTickets(manualLottoTickets)
}

private fun getManualLottoQuantity(): Int {
println("수동으로 구매할 로또 수를 입력해 주세요.")
val quantity = readln()
return quantity.toIntOrNull() ?: throw IllegalArgumentException("유효한 숫자를 입력해주세요")
}

private fun getPurchaseAmount(): Money {
println("구입 금액을 입력해 주세요.")
val amount = readln()
return amount.toIntOrNull() ?: throw IllegalArgumentException("구입 금액이 유효하지 않습니다. 숫자를 입력해주세요")
return Money(amount)
}

fun getUserWinningLotto(): WinningLotto {
println("지난 주 당첨 번호를 입력해 주세요.")
val winningLottoNumbers: String = readln()
val numbers = winningLottoNumbers.split(",").map { it.toInt() }.toSet()
val numbers = winningLottoNumbers.split(DELIMITER).map { it.toInt() }.toSet()

println("보너스 볼을 입력해 주세요.")
val bonusNumber: String = readln()
Expand Down
13 changes: 9 additions & 4 deletions src/main/kotlin/lotto/view/OutputView.kt
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,18 @@ import lotto.domain.LottoRank
import lotto.domain.LottoResult
import lotto.domain.LottoResults
import lotto.domain.LottoTickets
import lotto.dto.PurchaseDetail

object OutputView {
fun printPurchaseResult(lottoTickets: LottoTickets) {
val ticketCount = lottoTickets.size
println(message = "$ticketCount 개를 구매했습니다.")
fun printPurchaseResult(
lottoTickets: LottoTickets,
purchasedDetail: PurchaseDetail,
) {
val manualLottoQuantity = purchasedDetail.manualLottoQuantity
val autoLottoQuantity = purchasedDetail.autoLottoQuantity
println("수동으로 ${manualLottoQuantity}장, 자동으로 ${autoLottoQuantity}개를 구매했습니다.")
for (lottoTicket in lottoTickets) {
println(lottoTicket.map { it.number })
println(lottoTicket.map { it.getNumber() })
}
}

Expand Down
30 changes: 0 additions & 30 deletions src/test/kotlin/lotto/controller/LottoControllerTest.kt

This file was deleted.

10 changes: 10 additions & 0 deletions src/test/kotlin/lotto/domain/LottoTicketTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package lotto.domain

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.equals.shouldBeEqual
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test
import org.junit.jupiter.params.ParameterizedTest
Expand Down Expand Up @@ -114,6 +115,15 @@ class LottoTicketTest {
rank.prize shouldBe 0
}

@Test
fun `같은 숫자와 콤마 구분자로 된 문자열로 생성된 로또 티켓은 동일하다`() {
val numbers = "1,2,3,4,5,6"
val lottoTicket = LottoTicket.makeLottoTicket(numbers)
val number2 = "1,2,3,4,5,6"
val lottoTicket2 = LottoTicket.makeLottoTicket(number2)
lottoTicket shouldBeEqual lottoTicket2
}

companion object {
@JvmStatic
fun providedDuplicationNumbers() =
Expand Down
15 changes: 5 additions & 10 deletions src/test/kotlin/lotto/domain/LottoTicketsTest.kt
Original file line number Diff line number Diff line change
@@ -1,24 +1,19 @@
package lotto.domain

import io.kotest.assertions.throwables.shouldThrow
import io.kotest.matchers.collections.shouldHaveSize
import io.kotest.matchers.shouldBe
import org.junit.jupiter.api.Test

class LottoTicketsTest {
@Test
fun `로또 티켓은 구입금액은 1_000원 이상이여야 한다`() {
shouldThrow<IllegalArgumentException> {
LottoTickets.purchase(900)
}.also {
it.message shouldBe "구입금액은 1000원 이상이여야 합니다"
}
fun `자동 로또 티켓은 구입 수량이 0개이상이여야 발급되는 로또 티켓은 0개이다`() {
val autoLottoTickets = LottoTickets.generateAutoLottoTickets(0)
autoLottoTickets.size shouldBe 0
}

@Test
fun `로또 티켓을 구입 금액에 맞게 발급한다`() {
val amount = 5_000
val lottoTickets = LottoTickets.purchase(amount)
fun `로또 티켓을 구입 수량에 맞게 발급한다`() {
val lottoTickets = LottoTickets.generateAutoLottoTickets(5)
lottoTickets shouldHaveSize 5
}

Expand Down
Loading