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

[로또] 박주희 미션 제출합니다. #1366

Open
wants to merge 28 commits into
base: main
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
0ab2ae2
docs: README 기능 목록 수정
aprilnineteen Oct 31, 2024
0c1c09b
chore: .gitignore 설정
aprilnineteen Oct 31, 2024
7da7a15
feat: 구입할 로또 금액 입력 기능 구현
aprilnineteen Oct 31, 2024
de1b1b3
feat: 당첨 번호 입력 기능 구현
aprilnineteen Oct 31, 2024
0ea8b8b
feat: 보너스 번호 입력 기능 구현
aprilnineteen Oct 31, 2024
e790776
feat: 로또 랭크 Enum 생성
aprilnineteen Nov 4, 2024
dd5b337
feat: 로또 에러 메시지 Enum 생성
aprilnineteen Nov 4, 2024
699b792
feat: 로또 예외 클래스 생성
aprilnineteen Nov 4, 2024
eef86de
fix: 상수 String 내용 수정
aprilnineteen Nov 4, 2024
6c36c1b
fix: 에러 내용 수정
aprilnineteen Nov 4, 2024
4caf4bb
refactor: Lotto 클래스 위치 변경
aprilnineteen Nov 5, 2024
0807de8
fix: 검증 부분 수정
aprilnineteen Nov 5, 2024
588f6a9
fix: 당첨 번호와 일치하는지 여부 찾는 기능 및 숫자 표현 변경
aprilnineteen Nov 5, 2024
c0e3e9d
fix: 사용자에게 입력받은 로또 값 검증 기능
aprilnineteen Nov 5, 2024
1c915ba
fix: 사용자에게 입력받은 로또 값 모델 추가
aprilnineteen Nov 5, 2024
bf420a5
fix: 사용자에게 입력받은 당첨 번호와 로또 검증 기능
aprilnineteen Nov 5, 2024
79b6398
fix: 랜덤으로 생성되는 로또 모델 구현
aprilnineteen Nov 5, 2024
ae3cba1
fix: 로또 머신 모델 구현
aprilnineteen Nov 5, 2024
e7a5571
fix: 입력받은 값 서비스 구현
aprilnineteen Nov 5, 2024
8a94c7e
fix: 사용자에게 입력받은 보너스 번호 모델 추가
aprilnineteen Nov 5, 2024
a64206f
fix: 로또 티켓 개수만큼 출력하는 서비스 구현
aprilnineteen Nov 5, 2024
58d017d
fix: 구입한 로또 출력하는 기능 구현
aprilnineteen Nov 5, 2024
547a271
fix: 로또 결과 출력하는 서비스 구현
aprilnineteen Nov 5, 2024
94c3291
fix: 사용자가 넣은 값이 유효하지 않으면 다시 입력받는 기능 구현
aprilnineteen Nov 5, 2024
855ed20
fix: 컨트롤러 추가
aprilnineteen Nov 5, 2024
49aa69d
fix: 시작하는 Application에서 컨트롤러 불러오기
aprilnineteen Nov 5, 2024
5378929
refactor: 안 쓰는 클래스 정리
aprilnineteen Nov 5, 2024
c6a097e
fix: 안 쓰는 메서드 정리
aprilnineteen Nov 5, 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
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ build/
!**/src/main/**
!**/src/test/**

*.class

### STS ###
.apt_generated
.classpath
Expand Down
97 changes: 96 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,96 @@
# java-lotto-precourse
# 🎟️ java-lotto-precourse

## 📋 기능 요구 사항

> 간단한 로또 발매기를 구현한다.

- 로또 번호의 숫자 범위는 1~45까지이다.
- 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다.
- 당첨 번호 추첨 시 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑는다.
- 당첨은 1등부터 5등까지 있다. 당첨 기준과 금액은 아래와 같다.
- 1등: 6개 번호 일치 / 2,000,000,000원
- 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원
- 3등: 5개 번호 일치 / 1,500,000원
- 4등: 4개 번호 일치 / 50,000원
- 5등: 3개 번호 일치 / 5,000원
- 로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 한다.
- 로또 1장의 가격은 1,000원이다.
- 당첨 번호와 보너스 번호를 입력받는다.
- 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다.
- 사용자가 잘못된 값을 입력할 경우 IllegalArgumentException을 발생시키고, "[ERROR]"로 시작하는 에러 메시지를 출력 후 그 부분부터 입력을 다시 받는다.
- Exception이 아닌 IllegalArgumentException, IllegalStateException 등과 같은 명확한 유형을 처리한다.

## 🛠️ 프로그래밍 요구 사항 1
- **JDK 21** 버전에서 실행 가능해야 한다.
- 프로그램 실행의 시작점은 Application의 main()이다.
- **build.gradle** 파일은 변경할 수 없으며, 제공된 라이브러리 이외의 외부 라이브러리는 사용하지 않는다.
- 프로그램 종료 시 System.exit()를 호출하지 않는다.
- 프로그래밍 요구 사항에서 달리 명시하지 않는 한 파일, 패키지 등의 이름을 바꾸거나 이동하지 않는다.
- 자바 코드 컨벤션을 지키면서 프로그래밍한다.
- 기본적으로 [Java Style Guide](https://github.com/woowacourse/woowacourse-docs/tree/main/styleguide/java)를 원칙으로 한다.

## 📝 프로그래밍 요구 사항 2
- **indent**(인덴트, 들여쓰기) **depth**를 3이 넘지 않도록 구현한다. **2**까지만 허용한다.
- 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다.
- 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메서드)를 분리하면 된다.
- **3항 연산자**를 쓰지 않는다.
- 함수(또는 메서드)가 **한 가지** 일만 하도록 최대한 작게 만들어라.
- `JUnit 5`와 `AssertJ`를 이용하여 정리한 기능 목록이 정상적으로 작동하는지 테스트 코드로 확인한다.

## 🔍 프로그래밍 요구 사항 3
- 함수(또는 메서드)의 길이가 **15 라인**을 넘어가지 않도록 구현한다.
- 함수(또는 메서드)가 **한 가지** 일만 잘 하도록 구현한다.
- **else 예약어**를 쓰지 않는다.
- else를 쓰지 말라고 하니 switch/case로 구현하는 경우가 있는데 **switch/case**도 허용하지 않는다.
- 힌트: if 조건절에서 값을 return하는 방식으로 구현하면 else를 사용하지 않아도 된다.
- **Java** `Enum`을 적용하여 프로그램을 구현한다.
- 구현한 기능에 대한 단위 테스트를 작성한다. 단, UI(System.out, System.in, Scanner) 로직은 제외한다.
- 단위 테스트 작성이 익숙하지 않다면 `LottoTest`를 참고하여 학습한 후 테스트를 작성한다.

## 🚀 구현할 기능 목록

### 1. 입력 처리
- [x] 사용자가 구입할 로또의 총 금액을 **1,000 단위**로 입력
- [x] 당첨 번호인 6개의 숫자 입력 (번호는 쉼표(,)로 구분)
- [x] 보너스 번호인 1개의 숫자 입력

### 2. 로또 번호 생성
- [ ] 사용자가 구매한 금액에 따라 해당 수량만큼 로또 번호 생성
- [ ] 1부터 45까지의 숫자 중 중복되지 않는 6개의 로또 번호를 랜덤하게 생성

### 3. 당첨 번호 저장
- [ ] 입력받은 당첨 번호 및 보너스 번호 리스트 생성

### 4. 당첨 결과 및 수익률 계산
- [ ] 구매한 로또 번호와 당첨 번호를 비교하여 일치하는 개수 계산
- [ ] 각 등수별 당첨 금액으로 총 수익을 계산
- [ ] 계산된 수익과 구입한 로또의 총 금액을 기반으로 소수점 둘째 자리에서 반올림하여 수익률 계산

### 5. 출력 처리
- [ ] 사용자가 구매한 로또의 개수와 랜덤 번호 오름차순 출력
- [ ] 등수별 당첨 개수와 해당 상금 출력
- [ ] 당첨 금액과 총 수익률 출력

### 6. 예외 처리
- [ ] 구입 금액이 1,000의 배수가 아닐 시
- [ ] 구입 금액이 100,000원을 초과할 시 (구입한 로또 개수가 100개를 초과할 시)
- [ ] 로또 구매 개수가 1개 이하일 시
- [ ] 당첨 번호의 구분자로 쉼표(,)를 사용하지 않을 시
- [ ] 당첨 번호가 중복되거나 보너스 번호가 당첨 번호와 중복될 시
- [ ] 당첨 번호와 보너스 번호가 1부터 45 사이의 범위를 벗어날 시
- [ ] 당첨 번호 6개, 보너스 번호 1개의 개수에서 벗어날 시
- [ ] 입력된 값의 형식이 숫자가 아닐 시 (단, 당첨 번호 입력에서 구분자는 허용함)
- [ ] 입력된 값이 공백 또는 빈 문자열일 시

### 7. 테스트
- [ ] **기능 단위 테스트**로 프로그램의 비즈니스 로직이 올바르게 작동하는지 검증
- [ ] **예외 단위 테스트**로 프로그램이 예외 상황에 적절한 에러 메시지를 출력하는지 검증

## 🎯 3주차 과제의 목표
2주차의 코드 리뷰를 상기하며 피드백에 따라 코드를 작성하기

1. if문 내에서 중괄호를 생략하면 안 됨 (`Google Java Style Guide` 어긋남)
2. 메서드 네이밍 중 `is`가 접두어로 쓰이면 `boolean` 타입을 반환할 것 같으니 메서드명을 명확하게 표현하자
3. 하드 코딩 대신 범위나 조건 값을 상수로 처리해 일관성 가지기 (2주차 공통 피드백과 일맥상통)
4. 예외에 대한 테스트뿐만 아니라 도메인 로직에 대한 테스트 코드 추가하자
5. `String` 클래스 메서드와 같은 함수를 활용해 코드의 가독성을 높이자
11 changes: 9 additions & 2 deletions src/main/java/lotto/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,14 @@
package lotto;

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

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
InputView inputView = new InputView();
OutputView outputView = new OutputView();
LottoController lottoController = new LottoController(inputView, outputView);
Comment on lines +9 to +11
Copy link

Choose a reason for hiding this comment

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

애플리케이션 내에서 보면 주희 님이 생성하신 인스턴스가 다시 할당되지 않기 때문에 inputView, outputVie, lottoController에 final을 명시하는 것은 어떨지 궁금해요!

lottoController.run();
}
}
}
20 changes: 0 additions & 20 deletions src/main/java/lotto/Lotto.java

This file was deleted.

17 changes: 17 additions & 0 deletions src/main/java/lotto/controller/LottoController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package lotto.controller;

import lotto.service.LottoService;
import lotto.view.InputView;
import lotto.view.OutputView;

public class LottoController {
private final LottoService lottoService;

public LottoController(InputView inputView, OutputView outputView) {
this.lottoService = new LottoService(inputView, outputView);
Comment on lines +9 to +11
Copy link

Choose a reason for hiding this comment

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

mvc 패턴에서 모델과 뷰의 관계에 대해 생각해 보시면 좋을 것 같아요

}

public void run() {
lottoService.run();
}
}
23 changes: 23 additions & 0 deletions src/main/java/lotto/enums/LottoErrorMessage.java
Copy link

Choose a reason for hiding this comment

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

패키지명은 최대한 단수형을 지정하는 것도 컨벤션의 일부인 것을 아셨나요?.? 저도 방금 알았습니다...

Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package lotto.enums;

public enum LottoErrorMessage {
EMPTY_OR_BLANK_INPUT("[ERROR] 입력 값은 공백이나 빈 문자열일 수 없습니다."),
NON_NUMERIC_INPUT("[ERROR] 입력 값은 숫자 형식이어야 합니다."),
INVALID_PURCHASE_AMOUNT("[ERROR] 구입 금액은 1,000의 배수여야 합니다."),
INVALID_DELIMITER("[ERROR] 당첨 번호는 쉼표(,)로 구분해야 합니다."),
INVALID_NUMBER_COUNT("[ERROR] 당첨 번호는 6개, 보너스 번호는 1개여야 합니다."),
EXCEEDS_MAX_PURCHASE_AMOUNT("[ERROR] 구입 금액은 100,000원을 초과할 수 없습니다."),
BELOW_MINIMUM_PURCHASE_AMOUNT("[ERROR] 로또 구매 개수는 1개 이상이어야 합니다."),
DUPLICATE_WINNING_NUMBERS("[ERROR] 당첨 번호와 보너스 번호는 중복될 수 없습니다."),
NUMBER_OUT_OF_RANGE("[ERROR] 당첨 번호와 보너스 번호는 1부터 45 사이의 숫자여야 합니다.");

private final String message;

LottoErrorMessage(String message) {
this.message = message;
}

public String getMessage() {
return message;
}
}
41 changes: 41 additions & 0 deletions src/main/java/lotto/enums/LottoRank.java
Copy link

Choose a reason for hiding this comment

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

이넘 클래스 내에서도 메소드를 구현할 수 있다는 걸 얼마 전에 알게 되었는데 주희 님께서는 이부분을 잘 활용하신 것 같아서 인상 깊습니다 😋

Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
package lotto.enums;

public enum LottoRank {
NO_MATCH(0, 0, false),
THREE_MATCH(3, 5000, false),
FOUR_MATCH(4, 50000, false),
FIVE_MATCH(5, 1500000, false),
FIVE_MATCH_WITH_BONUS(5, 30000000, true),
SIX_MATCH(6, 2000000000, false);
Comment on lines +4 to +9
Copy link

Choose a reason for hiding this comment

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

숫자 구분을 위해 3자리마다 _를 넣어주면 좋을 것 같습니다.
1_500_000처럼 나타낼 수 있습니다.


private final int matchCount;
private final int reward;
private final boolean matchesBonus;

LottoRank(int matchCount, int reward, boolean matchesBonus) {
this.matchCount = matchCount;
this.reward = reward;
this.matchesBonus = matchesBonus;
}

public int getMatchCount() {
return matchCount;
}

public int getReward() {
return reward;
}

public boolean matchesBonus() {
return matchesBonus;
}

public static LottoRank matchCountAndRank(int matchCount, boolean bonusMatch) {
for (LottoRank rank : values()) {
if (rank.getMatchCount() == matchCount && rank.matchesBonus() == bonusMatch) {
return rank;
}
}
return NO_MATCH;
}
}
10 changes: 10 additions & 0 deletions src/main/java/lotto/exception/LottoException.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package lotto.exception;

import lotto.enums.LottoErrorMessage;

public class LottoException extends IllegalArgumentException {

public LottoException(LottoErrorMessage lottoErrorMessage) {
super(lottoErrorMessage.getMessage());
}
}
18 changes: 18 additions & 0 deletions src/main/java/lotto/model/Lotto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package lotto.model;

import lotto.validation.WinningNumberValidation;

import java.util.List;

public class Lotto {
private final List<Integer> numbers;

public Lotto(List<Integer> numbers) {
WinningNumberValidation.validate(numbers);
this.numbers = numbers;
}

public List<Integer> getNumbers() {
return numbers;
}
}
31 changes: 31 additions & 0 deletions src/main/java/lotto/model/LottoBonusNumber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package lotto.model;

import lotto.enums.LottoErrorMessage;
import lotto.exception.LottoException;

import java.util.List;

public class LottoBonusNumber {
private static final int MIN_NUMBER = 1;
private static final int MAX_NUMBER = 45;

private final int bonusNumber;

public LottoBonusNumber(int bonusNumber, List<Integer> winningNumbers) {
validateBonusNumber(bonusNumber, winningNumbers);
this.bonusNumber = bonusNumber;
}

private void validateBonusNumber(int bonusNumber, List<Integer> winningNumbers) {
if (bonusNumber < MIN_NUMBER || bonusNumber > MAX_NUMBER) {
throw new LottoException(LottoErrorMessage.NUMBER_OUT_OF_RANGE);
}
if (winningNumbers.contains(bonusNumber)) {
throw new LottoException(LottoErrorMessage.DUPLICATE_WINNING_NUMBERS);
}
}

public int getBonusNumber() {
return bonusNumber;
}
}
34 changes: 34 additions & 0 deletions src/main/java/lotto/model/LottoMachine.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package lotto.model;

import lotto.enums.LottoRank;

import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class LottoMachine {
private final Lotto winningLottoNumbers;
Copy link

Choose a reason for hiding this comment

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

여기에 보너스 번호까지 들어가 있는 건가요?.?


public LottoMachine(Lotto winningLottoNumbers) {
this.winningLottoNumbers = winningLottoNumbers;
}

public Map<LottoRank, Integer> match(List<LottoTicket> tickets) {
Map<LottoRank, Integer> result = new HashMap<>();

for (LottoTicket ticket : tickets) {
LottoRank rank = getRank(ticket.getLottoNumbers());
result.put(rank, result.getOrDefault(rank, 0) + 1);
}

return result;
}

private LottoRank getRank(List<Integer> lottoNumbers) {
int matchCount = (int) lottoNumbers.stream()
.filter(winningLottoNumbers.getNumbers()::contains)
.count();

return LottoRank.matchCountAndRank(matchCount, false);
}
Comment on lines +27 to +33
Copy link

Choose a reason for hiding this comment

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

보너스 번호를 가지고 있는지에 대한 여부를 기본값으로 다 false로 지정해 주셨는데,LottoRank를 통해 등수까지 반환해 주는 것이라면 이쪽에서 보너스 번호의 매칭 여부까지 확인해 주면 좋을 것 같아요!

}
15 changes: 15 additions & 0 deletions src/main/java/lotto/model/LottoPurchase.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package lotto.model;

public class LottoPurchase {
private static final int LOTTO_PRICE = 1000;

private final int purchaseAmount;

public LottoPurchase(int purchaseAmount) {
this.purchaseAmount = purchaseAmount;
}

public int lottoCount() {
return purchaseAmount / LOTTO_PRICE;
}
}
25 changes: 25 additions & 0 deletions src/main/java/lotto/model/LottoTicket.java
Copy link

Choose a reason for hiding this comment

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

LottoTicketLotto 클래스가 인스턴스 변수로 정수의 리스트를 갖고 있는데
조금은 역할이 겹쳐 보이기도 합니다.

Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package lotto.model;

import camp.nextstep.edu.missionutils.Randoms;

import java.util.List;

public class LottoTicket {
private static final int MIN_LOTTO_NUM = 1;
private static final int MAX_LOTTO_NUM = 45;
private static final int MAIN_NUMBER_COUNT = 6;

private final List<Integer> lottoNumbers;

public LottoTicket() {
this.lottoNumbers = createLottoNumbers();
}

private List<Integer> createLottoNumbers() {
return Randoms.pickUniqueNumbersInRange(MIN_LOTTO_NUM, MAX_LOTTO_NUM, MAIN_NUMBER_COUNT);
}

public List<Integer> getLottoNumbers() {
return lottoNumbers;
}
}
Loading