forked from woowacourse-precourse/kotlin-lotto-6
-
Notifications
You must be signed in to change notification settings - Fork 3
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
[로또] 남궁혜민 미션 제출합니다. #2
Open
hyeminililo
wants to merge
36
commits into
gdgoc-skhu-missions:main
Choose a base branch
from
hyeminililo:main
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
36 commits
Select commit
Hold shift + click to select a range
0e74b99
Model, Test 추가
labyrinth30 9d0bf41
Purchase 클래스 추가
labyrinth30 eff3f62
test: Purchase 모델 테스트 코드 작성
cucumber99 f5c67b8
feat: Purchase 모델 구현
cucumber99 4e6dfcb
feat: 출력 뷰 구현
cucumber99 92c1bad
test: Tickets 모델 테스트
cucumber99 cb6823e
feat: Tickets 모델 구현
cucumber99 01e5bb0
refactor: Bonus 모델 수정
cucumber99 d99ee5e
test: Bonus 모델 테스트 수정
cucumber99 72df0ce
refactor: Lotto 모델 수정
cucumber99 62f533e
✅ Feat : Winning 테스트 구현
hyeminililo 63ecd59
🎨 Winning 모델 구현
hyeminililo 7081bb4
feat : Ranking 테스트 구현
hyeminililo 71e9a19
refactor: 중복 검사 로직 수정
cucumber99 324ffb9
test: 테스트 코드 수정
cucumber99 2c3b332
test: Rank 모델 테스트
cucumber99 2583eb9
feat: Rank 모델 구현
cucumber99 e130820
Merge branch 'main' of https://github.com/hyeminililo/kotlin-lotto
hyeminililo 7f2d4bf
test: 상금 및 수익률 로직 테스트
cucumber99 934dc9f
feat: 상금 및 수익률 모델 구현
cucumber99 f52766c
Merge branch 'main' of https://github.com/hyeminililo/kotlin-lotto
hyeminililo 68c8f72
feat : Winning 클래스 삭제
hyeminililo a368a9a
프로그램 구현
cucumber99 7d4d9a6
Merge branch 'main' of https://github.com/cucumber99/kotlin-lotto
cucumber99 0b61d78
feat : 불필요한 테스트 코드 삭제
hyeminililo 4ee0cda
Merge branch 'main' of https://github.com/cucumber99/kotlin-lotto
hyeminililo d5deecc
♻️ 1차 리팩토링
hyeminililo 4232930
📝 Ticket 클래스 작성
hyeminililo 83ce4a4
📝 TicketController 작성
hyeminililo 2cd0a9e
🎨 구조 변경
hyeminililo ca64be2
🔥 Docs : Tickets 클래스 삭제
hyeminililo 17ae544
♻️ Refactor : 2차 리팩토링
hyeminililo 2fcb14b
🔥 Feat : 불필요한 테스트 코드 삭제
hyeminililo 3c62fb7
📝 Test : ParameterizedTest 테스트 코드 구현
hyeminililo 49fe198
✅ Test : 2차 리팩토링에 대한 테스트 성공
hyeminililo e4d7fc3
✅ Refactor : 3차 리팩토링
hyeminililo File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
## 기능 구현 목록 | ||
### 입력 | ||
- 구입 금액을 입력받는다. | ||
- [x] 1000원 단위로 입력 | ||
- [x] 1000으로 나누어 떨어지지 않는 경우 예외 처리 | ||
- [x] 자연수가 아닌 경우 예외 처리 | ||
- [x] 숫자가 아닌 경우 예외 처리 | ||
- 당첨 번호를 입력받는다. | ||
- [x] 1~45 사이의 자연수 6개 입력 | ||
- [x] ','를 기준으로 구분 | ||
- [x] 공백이 존재하는 경우 예외 처리 | ||
- [x] 숫자가 아닌 경우 예외 처리 | ||
- [x] 중복된 값이 존재하는 경우 예외 처리 | ||
- 보너스 번호를 입력받는다. | ||
- [x] 1~45 사이의 자연수 1개 | ||
- [x] 숫자가 아닌 경우 예외 처리 | ||
- [x] 당첨 번호와 중복되는 경우 예외 처리 | ||
|
||
### 출력 | ||
- 생성된 로또 번호를 출력한다. | ||
- [x] 오름차순으로 출력 | ||
- 당첨 내역을 출력한다. | ||
- [x] 상금과 개수를 출력한다. | ||
- [x] 상금 출력시 ','를 천의 자리마다 배치한다. | ||
- [x] '당첨 통계'와 결과 출력 사이에 '---'를 배치한다. | ||
- 수익률을 출력한다. | ||
- [x] 소수점 둘째 자리에서 반올림한다. | ||
|
||
### 게임 로직 | ||
- 입력 받은 금액을 바탕으로 로또 개수를 계산한다. | ||
- [x] 구입금액 / 1000(로또 1장의 가격) | ||
- 계산된 개수만큼 로또 번호를 생성한다. | ||
- [x] 1~45 사이의 중복되지 않는 자연수 6개 (pickUniqueNumberInRange) | ||
- 로또 번호와 당첨 번호를 비교하여 순위를 계산한다. | ||
- [x] 1등(6개), 2등(5개+보너스), 3등(5개), 4등(4개), 5등(3개) | ||
- 수익률을 계산한다. | ||
- [x] 수익률 = (당첨 금액/구매 금액) | ||
|
||
### 예외상황 | ||
* 에러문구는 "[ERROR]"로 시작한다. | ||
* 에러문구 출력 후, 그 부분부터 다시 입력받는다. | ||
- [x] 당첨 번호, 보너스 번호를 입력 시, 숫자가 아닐 때 | ||
- [x] 금액이 1000원 단위가 아닐 때 | ||
- [x] 금액이 숫자가 아닐 때 | ||
- [x] 당첨 번호의 숫자 범위가 1~45 사이가 아닐 때 | ||
- [x] 당첨 번호를 입력할 때 쉼표 기준이 아닐 때 | ||
- [x] 당첨 번호에 동일한 수를 입력할 때 | ||
|
||
## Test 코드 | ||
|
||
### 입력 테스트 코드 | ||
|
||
- [x] 구입 금액에 해당하는 만큼 로또를 발행하는지 확인하는 테스트 코드 | ||
- [x] 에러 상황에서 다시 입력이 되는지 확인하는 코드 | ||
|
||
### 출력 테스트 코드 | ||
|
||
- [x] 동일한 수가 나오지 않는지 | ||
- [x] 번호가 일치하는지 확인하는 메소드 | ||
- [x] 수익률 테스트 코드 | ||
|
||
### Lotto 객체 관련 테스트 코드 | ||
|
||
- [x] 서로 다른 6개의 번호로 이루어진 로또 번호들이 잘 나오는지 | ||
- [x] 1~45 범위 안으로 로또 번호가 생성되는지 | ||
|
||
### Validation 테스트 코드 | ||
|
||
- [x] 당첨 번호, 보너스 번호를 입력할 때 숫자가 아닐 때 | ||
- [x] 금액이 1000원 단위가 아닐 때 | ||
- [x] 금액이 숫자가 아닐 때 | ||
- [x] 당첨 번호의 숫자 범위가 1~45 사이가 아닐 때 | ||
- [x] 당첨 번호를 입력할 때 쉼표 기준이 아닐 때 | ||
- [x] 당첨 번호에 동일한 수를 입력할 때 | ||
|
||
|
||
### 주의 사항 | ||
- 함수 또는 메소드의 길이가 10줄을 넘어가지 않도록 구현한다. | ||
- 함수의 단일 책임 원칙을 준수한다. | ||
- else 예약어를 사용하지 않는다. (early return) | ||
- Enum 클래스를 사용한다. | ||
- indent depth는 최대 1이 되도록 구현한다. | ||
- 도메인 로직에 단위 테스트를 구현해야한다. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,5 +1,8 @@ | ||
package lotto | ||
|
||
import lotto.controller.Controller | ||
|
||
fun main() { | ||
TODO("프로그램 구현") | ||
val controller = Controller() | ||
controller.start() | ||
} |
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,59 @@ | ||
package lotto.controller | ||
|
||
import lotto.model.* | ||
import lotto.view.InputView | ||
import lotto.view.OutputView | ||
|
||
class Controller { | ||
private val ticketController = TicketController() | ||
private val tickets = ticketController.handleTicketPurchase() | ||
|
||
fun start() { | ||
tickets | ||
val winningNumbers = inputWinningNumbers() | ||
val bonusNumber = inputBonusNumber() | ||
val rank = calculateRank(tickets, winningNumbers, bonusNumber) | ||
printStatistics(rank) | ||
printEarningsRate(rank, ticketController.purchaseAmout) | ||
} | ||
|
||
private fun inputWinningNumbers(): List<Int> { | ||
while (true) { | ||
try { | ||
OutputView.printWinningNumberMessage() | ||
return InputView.getWinningNumber() | ||
} catch (e: IllegalArgumentException) { | ||
println(e.message) | ||
} | ||
} | ||
} | ||
|
||
private fun inputBonusNumber(): Int { | ||
while (true) { | ||
try { | ||
OutputView.printBonusNumberMessage() | ||
return InputView.getInteger() | ||
} catch (e: IllegalArgumentException) { | ||
println(e.message) | ||
} | ||
} | ||
} | ||
|
||
private fun calculateRank(lottoTickets: List<List<Int>>, winningNumbers: List<Int>, bonusNumber: Int): Rank { | ||
val lotto = Lotto(winningNumbers) | ||
val bonus = Bonus(lotto, bonusNumber) | ||
val rank = Rank(lotto, bonus) | ||
rank.calculateRank(lottoTickets) | ||
return rank | ||
} | ||
|
||
private fun printStatistics(rank: Rank) { | ||
OutputView.printStatistics(rank.rankList) | ||
} | ||
|
||
private fun printEarningsRate(rank: Rank, purchaseAmount: Int?) { | ||
val prize = Prize(rank) | ||
val prizeRate = prize.getRate(prize.getPrizeMoney(), purchaseAmount!!) | ||
OutputView.printEarningsRate(prizeRate) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
package lotto.controller | ||
|
||
import lotto.model.Purchase | ||
import lotto.model.Ticket | ||
import lotto.view.InputView | ||
import lotto.view.OutputView | ||
import lotto.view.OutputView.printTicketMessage | ||
|
||
class TicketController() { | ||
|
||
private var ticket = Ticket() | ||
private var tickets: MutableList<List<Int>> = mutableListOf() | ||
var purchaseAmout: Int? = null | ||
|
||
fun handleTicketPurchase(): List<List<Int>> { | ||
purchaseAmout = inputPurchaseAmount() | ||
tickets = generateTickets(purchaseAmout!!) | ||
printTicketMessage(this.tickets) | ||
return tickets | ||
} | ||
|
||
private fun inputPurchaseAmount(): Int { | ||
while (true) { | ||
try { | ||
OutputView.printAmountMessage() | ||
// purchaseAmount = InputView.getInteger() | ||
return InputView.getInteger() | ||
} catch (e: IllegalArgumentException) { | ||
println(e.message) | ||
} | ||
} | ||
} | ||
|
||
fun generateTickets(purchaseAmount: Int): MutableList<List<Int>> { | ||
val purchase = Purchase(purchaseAmount) | ||
repeat(purchase.calculatePurchaseCount()) { | ||
val ticket = ticket.generateLottoNumber() | ||
addTicket(ticket) | ||
} | ||
return tickets | ||
} | ||
|
||
private fun addTicket(ticket: List<Int>) { | ||
tickets.add(ticket) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,18 @@ | ||
package lotto.model | ||
|
||
import lotto.util.Validator.validateBonusRange | ||
import lotto.util.Validator.validateLottoBonusDuplicate | ||
|
||
class Bonus( | ||
private val lotto: Lotto, | ||
private val _bonusNumber: Int | ||
) { | ||
|
||
val bonusNumber: Int | ||
get() = _bonusNumber | ||
|
||
init { | ||
validateBonusRange(_bonusNumber) | ||
validateLottoBonusDuplicate(lotto.getWinningNumbers(), _bonusNumber) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package lotto.model | ||
|
||
import lotto.util.Validator.validateLottoDuplicate | ||
import lotto.util.Validator.validateLottoRange | ||
import lotto.util.Validator.validateLottoSize | ||
|
||
class Lotto(private val numbers: List<Int>) { | ||
init { | ||
validateLottoSize(numbers) | ||
validateLottoRange(numbers) | ||
validateLottoDuplicate(numbers) | ||
} | ||
|
||
fun getWinningNumbers(): List<Int> { | ||
return numbers | ||
} | ||
|
||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
package lotto.model | ||
|
||
class Prize( | ||
private val rank: Rank | ||
) { | ||
companion object { | ||
private const val PRIZE_1ST = 2_000_000_000 | ||
private const val PRIZE_2ND = 30_000_000 | ||
private const val PRIZE_3RD = 1_500_000 | ||
private const val PRIZE_4TH = 50_000 | ||
private const val PRIZE_5TH = 5_000 | ||
|
||
private const val PERCENT = 100 | ||
} | ||
|
||
fun getPrizeMoney(): Int { | ||
return rank.rankList[0] * PRIZE_1ST + rank.rankList[1] * PRIZE_2ND + | ||
rank.rankList[2] * PRIZE_3RD + rank.rankList[3] * PRIZE_4TH + rank.rankList[4] * PRIZE_5TH | ||
} | ||
|
||
fun getRate(prizeMoney: Int, amount: Int): String { | ||
val rate = (prizeMoney.toDouble() / amount.toDouble()) * PERCENT | ||
return String.format("%.1f", rate) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,21 @@ | ||
package lotto.model | ||
|
||
import lotto.util.Validator.validateAmount | ||
|
||
class Purchase(private val amount: Int) { | ||
companion object { | ||
private const val LOTTO_PRICE = 1000 | ||
} | ||
|
||
init { | ||
validateAmount(amount) | ||
} | ||
|
||
fun purchaseAmount(): Int { | ||
return amount; | ||
} | ||
|
||
fun calculatePurchaseCount(): Int { | ||
return amount / LOTTO_PRICE | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,50 @@ | ||
package lotto.model | ||
|
||
class Rank( | ||
private val lotto: Lotto, | ||
private val bonus: Bonus | ||
) { | ||
private val _rankList: MutableList<Int> = MutableList(6) { 0 } | ||
|
||
val rankList: List<Int> | ||
get() = _rankList | ||
|
||
companion object { | ||
private const val INDEX_1ST = 0 | ||
private const val INDEX_2ND = 1 | ||
private const val INDEX_3RD = 2 | ||
private const val INDEX_4TH = 3 | ||
private const val INDEX_5TH = 4 | ||
private const val INDEX_NONE = 5 | ||
|
||
private const val COUNT_SIX = 6 | ||
private const val COUNT_FIVE = 5 | ||
private const val COUNT_FOUR = 4 | ||
private const val COUNT_THREE = 3 | ||
} | ||
|
||
fun calculateRank(tickets: List<List<Int>>) { | ||
val winningNumbers = lotto.getWinningNumbers() | ||
val bonusNumber = bonus.bonusNumber | ||
|
||
tickets.forEach { ticket -> | ||
val matchCount = ticket.count { it in winningNumbers } | ||
val rankIndex = getRankIndex(matchCount, ticket.contains(bonusNumber)) | ||
_rankList[rankIndex]++ | ||
} | ||
} | ||
|
||
private fun getRankIndex(matchCount: Int, hasBonus: Boolean): Int { | ||
if (matchCount == COUNT_SIX) return INDEX_1ST | ||
if (matchCount == COUNT_FIVE) return countBonus(hasBonus) | ||
if (matchCount == COUNT_FOUR) return INDEX_4TH | ||
if (matchCount == COUNT_THREE) return INDEX_5TH | ||
return INDEX_NONE | ||
} | ||
|
||
private fun countBonus(hasBonus: Boolean): Int { | ||
if (hasBonus) return INDEX_2ND | ||
return INDEX_3RD | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,15 @@ | ||
package lotto.model | ||
|
||
import camp.nextstep.edu.missionutils.Randoms | ||
|
||
class Ticket { | ||
companion object { | ||
const val MIN_NUMBER = 1 | ||
const val MAX_NUMBER = 45 | ||
const val LOTTO_SIZE = 6 | ||
} | ||
|
||
fun generateLottoNumber(): List<Int> { | ||
return Randoms.pickUniqueNumbersInRange(MIN_NUMBER, MAX_NUMBER, LOTTO_SIZE).sorted() | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,14 @@ | ||
package lotto.util | ||
|
||
enum class ErrorMessage(private val message: String) { | ||
NUMBER_NULL("입력값이 존재하지 않습니다."), | ||
NUMBER_INTEGER("입력값은 숫자여야 합니다."), | ||
NUMBER_NATURAL("입력값은 자연수여야 합니다."), | ||
AMOUNT_UNIT("구매 금액은 1000원 단위로 입력되어야 합니다."), | ||
LOTTO_RANGE("로또 번호의 숫자 범위는 1~45 사이여야 합니다."), | ||
LOTTO_SIZE("로또 번호는 6개여야 합니다."), | ||
LOTTO_DUPLICATE("당첨 번호는 각기 다른 수를 입력해야 합니다."), | ||
BONUS_DUPLICATE("보너스 번호는 당첨 번호와 중복되어서는 안됩니다."); | ||
|
||
fun getMessage(): String = "[ERROR] $message" | ||
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. 이런 거 좋네요. 무작정 구현하다가 이렇게 메서드 내부에서 [ERROR] String을 선언해서 이 메서드의 사용자가 특정한 형식보단 에러 메시지에 집중할 수 있게 됐네요! |
||
} |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
전체적으로 VO 방식을 전체적으로 잘 활용하신 것 같아요.
값을 클래스로 감싸고 생성자 레벨에서 검증하여 코드의 신뢰성을 보장하고, 규칙을 한 곳에서 관리하면
당장 관리해야 할 소스코드 파일의 수는 늘어나지만 여러 이점이 있죠.
요구사항에서 'LOTTO 클래스를 그대로 사용하라'라고 했는데
이 요구사항의 의도를 잘 파악하신 걸로 생각됩니다.
물론 모든 타입을 클래스로 감쌀 필욘 없지만, Lotto, Bonus는 게임 내에서 중요한 역할을 하는 요소이고
이러한 요소들에 대한 규칙은 한 곳에서 정의하여 관리하는 것이 좋기 때문에 좋은 결정 같습니다.