Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
129 changes: 129 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,130 @@
# java-lotto-precourse

> 로또 맞게 해주세요...ㅠ

<details>
<summary>과제 세부 내용</summary>

## 과제 내용
로또 게임 기능을 구현해야 한다. 로또 게임은 아래와 같은 규칙으로 진행된다.
```
- 로또 번호의 숫자 범위는 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` 등과 같은 명확한 유형을 처리한다.

### 입출력
- 입력
- 로또 구입 금액
- 당첨 번호 6개
- 보너스 번호
- 출력
- 발행한 로또 수량 및 번호
- 당첨 내역
- 수익률
- (예외 문구)

ex)

```
구입금액을 입력해 주세요.
8000

8개를 구매했습니다.
[8, 21, 23, 41, 42, 43]
[3, 5, 11, 16, 32, 38]
[7, 11, 16, 35, 36, 44]
[1, 8, 11, 31, 41, 42]
[13, 14, 16, 38, 42, 45]
[7, 11, 30, 40, 42, 43]
[2, 13, 22, 32, 38, 45]
[1, 3, 5, 14, 22, 45]

당첨 번호를 입력해 주세요.
1,2,3,4,5,6

보너스 번호를 입력해 주세요.
7

당첨 통계
---
3개 일치 (5,000원) - 1개
4개 일치 (50,000원) - 0개
5개 일치 (1,500,000원) - 0개
5개 일치, 보너스 볼 일치 (30,000,000원) - 0개
6개 일치 (2,000,000,000원) - 0개
총 수익률은 62.5%입니다.
```

</details>

## 코드 흐름
- 로또 구매 금액을 입력받는다.
- 로또를 번호를 생성하고 출력한다.
- 당첨 번호를 입력받는다.
- 결과를 계산한 후 출력한다.

```mermaid
sequenceDiagram
participant View
participant Controller
participant Model

Controller->>View: 금액 입력 요청
View->>Controller: 구입 금액 반환
Controller->>Model: 구입 금액 전달
Model-->Model: 로또 생성
Model->>Controller: 로또 번호 반환
Controller->>View: 로또 번호 출력
View->>Controller: -
Controller->>View: 당첨 번호 입력 요청
View->>Controller: 당첨 번호 반환
Controller->>Model: 당첨 번호 전달
Model-->Model: 당첨 여부 확인
Model->>Controller: 결과 반환
Controller->>View: 결과 출력
View->>Controller: -
Controller-->Controller: 프로그램 종료

```

## 구현 기능 목록
- 입출력
- [ ] 구입 금액 입력
- [ ] 당첨 번호 입력
- [ ] 보너스 번호 입력
- [ ] 발행한 로또 수량 및 번호
- [ ] 당첨 내역
- [ ] 수익률
- [ ] 예외
- 로또
- [ ] 로또 생성
- [ ] 당첨 확인
- [ ] 수익률 계산

## 처리할 예외
- 나누어 떨어지지 않는 금액 (1000 단위로 떨어지지 않을 때)
- `[ERROR] 1,000원 단위로 입력해 주세요.`
- 너무 큰 구입 금액
- `[ERROR] $입력한 금액 보다 작은 금액을 입력해 주세요.`
- 부적절한 구입 금액 (음수 입력 등 포함, 입력 예외)
- `[ERROR] 유효한 구입 금액을 입력해 주세요`
- 부적절한 로또의 범위 (1-45 밖의 숫자)
- `[ERROR] 1부터 45 사이의 값을 입력해 주세요.`
- 중복되는 번호 / 보너스 번호
- `[ERROR] 중복되지 않은 번호를 입력해주세요.`
- 부적절한 양식 혹은 갯수
- `[ERROR] 당첨 번호 6개를 정확히 입력해주세요. ex)1,2,3,4,5,6`
- `[ERROR] 보너스 번호 하나를 정확히 입력해주세요. ex)7`
42 changes: 42 additions & 0 deletions src/main/java/lotto/AppConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package lotto;

import lotto.controller.LottoController;
import lotto.service.LottoControlService;
import lotto.service.LottoControlServiceImpl;
import lotto.service.RandomService;
import lotto.service.RandomServiceImpl;
import lotto.view.InputView;
import lotto.view.OutputView;
import lotto.view.provider.InputProvider;
import lotto.view.provider.WoowaInputProvider;

public class AppConfig {
public InputProvider inputProvider() {
return new WoowaInputProvider();
}

public InputView inputView() {
return new InputView(inputProvider());
}

public OutputView outputView() {
return new OutputView();
}

public LottoControlService lottoControlService() {
return new LottoControlServiceImpl();
}

public RandomService randomService() {
return new RandomServiceImpl();
}

public LottoController lottoController() {
return new LottoController(
inputView(),
outputView(),
lottoControlService(),
randomService()
);
}
}
6 changes: 6 additions & 0 deletions src/main/java/lotto/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,13 @@
package lotto;

import lotto.controller.LottoController;

public class Application {

public static void main(String[] args) {
// TODO: 프로그램 구현
AppConfig appConfig = new AppConfig();
LottoController lottoController = appConfig.lottoController();
lottoController.run();
}
}
20 changes: 0 additions & 20 deletions src/main/java/lotto/Lotto.java

This file was deleted.

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

import static lotto.utils.LottoConstants.LOTTO_PRICE;

import java.util.List;
import lotto.model.dto.WinningDataDto;
import lotto.service.LottoControlService;
import lotto.service.RandomService;
import lotto.utils.ExceptionConstants;
import lotto.utils.Utility;
import lotto.view.InputView;
import lotto.view.OutputView;

public class LottoController {
InputView inputView;
OutputView outputView;
LottoControlService lottoControlService;
RandomService randomService;
ErrorHandler errorHandler;

private static final int MAX_ATTEMPTS = 100;

public LottoController(InputView inputView, OutputView outputView, LottoControlService lottoControlService,
RandomService randomService) {
this.inputView = inputView;
this.outputView = outputView;
this.lottoControlService = lottoControlService;
this.randomService = randomService;
errorHandler = new ErrorHandler();
}

public void run() {
try {
int amount = inputPrice();
inputLottoNumber();
outputLottoData(amount);
inputBonusNumber();
composeLottoSystem();
printResult(amount);
} catch (Exception e) {
outputView.printException(e);
}
}

private int inputPrice() {
while (true) {
if (errorHandler.willGenerateError()) {
throw new IllegalStateException(ExceptionConstants.INTERNAL_SERVER_ERROR.getMessage());
}
try {
int price = inputView.getPrice();
validatePrice(price);
lottoControlService.buyLotto(price / LOTTO_PRICE, randomService::getLottoNumbers);
errorHandler.resetAttemptCount();
return price / LOTTO_PRICE;
} catch (Exception e) {
outputView.printException(e);
}
}
}

private void validatePrice(int price) {
if (!Utility.isDividedByThousand(price)) {
throw new IllegalArgumentException(ExceptionConstants.INDIVISIBLE_PRICE.getMessage());
}
}

private void outputLottoData(int amount) {
outputView.printResultGuide(amount);
lottoControlService.getLotto().forEach(lotto -> outputView.printLotto(lotto));
}

private void inputLottoNumber() {
while (true) {
if (errorHandler.willGenerateError()) {
throw new IllegalStateException(ExceptionConstants.INTERNAL_SERVER_ERROR.getMessage());
}
try {
String line = inputView.getLottoNumber();
lottoControlService.setWinningNumbers(line);
errorHandler.resetAttemptCount();
break;
} catch (Exception e) {
outputView.printException(e);
}
}
}

private void inputBonusNumber() {
while (true) {
if (errorHandler.willGenerateError()) {
throw new IllegalStateException(ExceptionConstants.INTERNAL_SERVER_ERROR.getMessage());
}
try {
int bonusNumber = inputView.getBonusNumber();
lottoControlService.setBonusNumber(bonusNumber);
errorHandler.resetAttemptCount();
break;
} catch (Exception e) {
outputView.printException(e);
}
}
}

private void composeLottoSystem() {
lottoControlService.composeLotto();
}

private void printResult(int amount) {
List<WinningDataDto> lottoResult = lottoControlService.checkWinning();
outputView.printResult(lottoResult);
float result = lottoControlService.calculateROI(lottoResult, amount * LOTTO_PRICE);
outputView.printRateOfReturn(result);
}

static class ErrorHandler {
private int attempts;

ErrorHandler() {
this.attempts = 0;
}

boolean willGenerateError() {
attempts++;
if (attempts > MAX_ATTEMPTS) {
return true;
}
return false;
}

void resetAttemptCount() {
attempts = 0;
}
}
}
53 changes: 53 additions & 0 deletions src/main/java/lotto/model/Lotto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package lotto.model;

import static lotto.utils.ExceptionConstants.DUPLICATED_LOTTO_NUMBER;
import static lotto.utils.ExceptionConstants.INVALID_LOTTO_RANGE;
import static lotto.utils.LottoConstants.LOWER_BOUND_NUMBER;
import static lotto.utils.LottoConstants.UPPER_BOUND_NUMBER;

import java.util.List;
import java.util.stream.Stream;
import lotto.utils.LottoPrize;
import lotto.utils.Utility;

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

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

private void validate(List<Integer> numbers) {
if (numbers.size() != 6) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 6개여야 합니다.");
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

다른건 다 utility 사용하고 exceptionConstants throw하게 만드셨던데 얘는 그냥 내비둔 이유가 있을까용? 없으면 저 사이즈 검사도 utility내의 validating 함수로 만드시는게 더 나을듯용

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

원본 파일을 수정하지 않으려고 그냥 뒀는데, 코드의 전체적 흐름에서 맞지 않는 것은 사실인 것 같네요...ㅎㅎ

if (Utility.hasDuplicatedValue(numbers)) {
throw new IllegalArgumentException(DUPLICATED_LOTTO_NUMBER.getMessage());
}
if (!Utility.isInRange(numbers, LOWER_BOUND_NUMBER, UPPER_BOUND_NUMBER)) {
throw new IllegalArgumentException(INVALID_LOTTO_RANGE.getMessage());
}
}

public LottoPrize checkWinning(List<Integer> winningNumbers, int bonusNumber) {
List<Integer> totalWinningNumber = Stream.concat(winningNumbers.stream(), Stream.of(bonusNumber)).toList();
int matchCount = (int) numbers.stream().filter(totalWinningNumber::contains).count();
boolean matchBonus = numbers.contains(bonusNumber);
return LottoPrize.getLottoPrize(matchCount, matchBonus);
}

public void validateAdditionalNumber(int value) {
List<Integer> tempNumbers = Stream.concat(numbers.stream(), Stream.of(value)).toList();
if (Utility.hasDuplicatedValue(tempNumbers)) {
throw new IllegalArgumentException(DUPLICATED_LOTTO_NUMBER.getMessage());
}
if (!Utility.isInRange(value, LOWER_BOUND_NUMBER, UPPER_BOUND_NUMBER)) {
throw new IllegalArgumentException(INVALID_LOTTO_RANGE.getMessage());
}
}

public List<Integer> getNumbers() {
return numbers;
}
}
Loading