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
71 changes: 71 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,72 @@
# java-lotto-precourse
- 개요: 간단한 로또 발매기 프로그램
- 프로그램 흐름 (사용자 입장)
- 구입 금액 입력
- 구입 금액에 따라 발행된 로또 개수 및 로또별 번호 확인
- 당첨 번호 입력
- 보너스 번호 입력
- 당첨 통계(당첨 내역 및 수익률) 확인
- 당첨 정책
- 1등: 6개 번호 일치 / 2,000,000,000원
- 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원
- 3등: 5개 번호 일치 / 1,500,000원
- 4등: 4개 번호 일치 / 50,000원
- 5등: 3개 번호 일치 / 5,000원

## 실행 결과 예시
```
구입금액을 입력해 주세요.
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%입니다.
```

## 기능 목록
- [X] 로또 구입 금액 입력값 검증
- 입력 형식이 숫자여야 함
- 로또 1장의 가격(1000원)으로 나누어 떨어져야 함
- [x] 구입 금액에 해당하는 로또 수 계산
- [x] 중복되지 않는 무작위 번호 6개로 구성된 단일 로또 발행
- 단, 로또 번호 정렬
- [x] 로또 수 만큼 로또 발행
- [x] 문자열 입력값에서 당첨 번호 추출
- 쉼표 기준으로 구분
- [x] 입력 받은 보너스 번호 검증
- 당첨 번호 입력값들과 중복되면 안 됨
- [x] 로또 번호의 숫자 범위 검증 (1~45)
- [x] 로또 번호 개수 검증
- [x] 당첨 번호 추첨 (중복되지 않는 숫자 6개와 보너스 번호 1개)
- [x] 발행된 로또 수량 및 번호 출력
- [x] 사용자가 구매한 로또 번호와 당첨 번호를 비교
- [x] 당첨 내역 계산
- [x] 당첨 내역 출력
- [x] 총 상금 계산
- [x] 수익률 계산
- [x] 수익률 출력
- 소수점 둘째 자리에서 반올림 (ex. 100.0%, 51.5%, 1,000,000.0%)

- [ ] 사용자가 잘못된 값을 입력할 경우 `IllegalArgumentException` 발생, 에러 메시지 출력 후 그 부분부터 입력을 다시 받기
- 단, 출력 메세지는 "[ERROR]"로 시작
4 changes: 3 additions & 1 deletion src/main/java/lotto/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
package lotto;

import lotto.controller.LottoController;

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

This file was deleted.

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

import lotto.enums.Rank;
import lotto.model.Lotto;
import lotto.dto.WinnerLotto;
import lotto.service.LottoCalculator;
import lotto.service.LottoFactory;
import lotto.validator.InputValidator;
import lotto.validator.LottoValidator;
import lotto.view.InputView;
import lotto.view.OutputView;

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

public class LottoController {
public static void run() {
int purchaseAmount = readPurchaseAmount();
List<Lotto> lottos = LottoFactory.createLottos(LottoCalculator.calculateLottoAmount(purchaseAmount));
OutputView.printPurchasedLotto(lottos);

WinnerLotto inputLotto = readWinnerLotto();
List<Rank> ranks = LottoCalculator.getWinningRanks(lottos, inputLotto, LottoFactory.createRandomWinnerLotto());
Map<Rank, Integer> statistics = LottoCalculator.getRankStatistics(ranks);

OutputView.printStatistics(LottoCalculator.calculateProfitRate(purchaseAmount, ranks), statistics);
}

private static int readPurchaseAmount() {
String input = InputView.ReadInput("구입금액");
LottoValidator.isValidPurchaseAmounts(input);
return Integer.parseInt(input);
}

private static WinnerLotto readWinnerLotto() {
String input = InputView.ReadInput("당첨 번호");
List<Integer> winNumbers = InputView.parseNumbers(input);
input = InputView.ReadInput("보너스 번호");
int bonus = InputValidator.validateBonusNumber(input, winNumbers);
return LottoFactory.createWinnerLotto(winNumbers, bonus);
}
}
9 changes: 9 additions & 0 deletions src/main/java/lotto/dto/WinnerLotto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package lotto.dto;

import java.util.List;

public record WinnerLotto(
List<Integer> lottoNumbers,
int bonusNumber
) {
}
26 changes: 26 additions & 0 deletions src/main/java/lotto/enums/ErrorMessage.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package lotto.enums;

import static lotto.validator.LottoValidator.*;

public enum ErrorMessage {
EMPTY_INPUT("빈 칸은 입력 불가합니다."),
NOT_NUMBER("숫자 형식이어야 합니다."),
NOT_DIVIDED_BY_LOTTO_PRICE("단위가 아닙니다."),
NOT_MINIMUM_PURCHASE_AMOUNTS("최소 구매 가격은 " + LOTTO_PRICE + "원 입니다."),
WRONG_LOTTO_NUMBER_RANGE("로또 번호는 " + LOTTO_MIN_NUMBER + "~ " + LOTTO_MAX_NUMBER + "이내의 숫자여야 합니다."),
WRONG_LOTTO_NUMBER_SIZE("로또 번호는 " + LOTTO_SIZE + "개여야 합니다."),
CAN_NOT_MATCH_BONUS_NUMBER("보너스 번호는 당첨 번호들과 불일치해야 합니다."),
LOTTO_NOT_EXIST("로또가 존재하지 않습니다."),
RANK_NOT_EXIST("존재하지 않는 등수입니다.");

private final String prefix = "[ERROR]";
private String message;

ErrorMessage(String message) {
this.message = prefix + message;
}

public String getMessage() {
return message;
}
}
44 changes: 44 additions & 0 deletions src/main/java/lotto/enums/Rank.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package lotto.enums;

import java.util.Arrays;
import java.util.List;

public enum Rank {
FIRST(6, 2000000000, false),
SECOND(5, 30000000, true),
THIRD(5, 1500000, false),
FOURTH(4, 50000, false),
FIFTH(3, 5000, false);

private final int matchCount;
private final int prize;
private final boolean isBonusRequired;

Rank(int machCount, int prize, boolean isBonusRequired) {
this.matchCount = machCount;
this.prize = prize;
this.isBonusRequired = isBonusRequired;
}

public int getMatchCount() {
return matchCount;
}

public int getPrize() {
return prize;
}

public boolean isBonusRequired() {
return isBonusRequired;
}

public static Rank calculateRank(int matchCount, boolean isBonusMatched) {
return Arrays
.stream(Rank.values())
.filter(rank -> (rank.matchCount == matchCount) && (rank.isBonusRequired == isBonusMatched))
.findFirst() // (matchCount, isBonusMatched) 조합으로는 정확히 하나의 Rank만 매칭
.orElseThrow(() -> new IllegalArgumentException(
ErrorMessage.RANK_NOT_EXIST.getMessage()
));
}
}
23 changes: 23 additions & 0 deletions src/main/java/lotto/model/Lotto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package lotto.model;

import java.util.List;

import static lotto.validator.LottoValidator.*;

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

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

public void validate(List<Integer> numbers) {
validateSize(numbers);
validateRange(numbers);
}

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

import lotto.enums.Rank;
import lotto.model.Lotto;
import lotto.dto.WinnerLotto;

import java.util.*;
import java.util.stream.Collectors;

import static lotto.validator.LottoValidator.LOTTO_PRICE;

public class LottoCalculator {
public static int calculateLottoAmount(int purchaseAmounts) {
return purchaseAmounts / LOTTO_PRICE;
}

public static List<Rank> getWinningRanks(List<Lotto> purchasedLotto, WinnerLotto inputLotto, WinnerLotto winnerLotto) {
List<Rank> ranks = new ArrayList<>();
for (Lotto lotto : purchasedLotto) {
int matchCount = (int) lotto.getNumbers().stream()
.filter(winnerLotto.lottoNumbers()::contains)
.count();
boolean isBonusMatched = lotto.getNumbers().contains(winnerLotto.bonusNumber());

try {
Rank rank = Rank.calculateRank(matchCount, isBonusMatched);
ranks.add(rank);
} catch (IllegalArgumentException e) {
// 의도적으로 무시: 해당 로또는 당첨 등수 없음
}
}
return ranks;
}

public static Map<Rank,Integer> getRankStatistics(List<Rank> ranks) {
Map<Rank, Integer> statistics = Arrays.stream(Rank.values())
.collect(Collectors.toMap(rank -> rank, rank -> 0));

ranks.forEach(rank -> statistics.merge(rank, 1, Integer::sum));

return statistics;
}

private static int calculateTotalPrize(List<Rank> ranks) {
if (ranks.isEmpty()) {
return 0;
}

int totalPrize = 0;

for (Rank rank : ranks) {
totalPrize += rank.getPrize();
}

return totalPrize;
}

public static double calculateProfitRate(int purchaseAmount, List<Rank> ranks) {
int totalPrize = calculateTotalPrize(ranks);
return (double) purchaseAmount / totalPrize * 100;
}
}
42 changes: 42 additions & 0 deletions src/main/java/lotto/service/LottoFactory.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package lotto.service;

import camp.nextstep.edu.missionutils.Randoms;
import lotto.model.Lotto;
import lotto.dto.WinnerLotto;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import static lotto.validator.LottoValidator.*;

public class LottoFactory {
public static Lotto createRandomLotto() {
List<Integer> nums = Randoms.pickUniqueNumbersInRange(LOTTO_MIN_NUMBER, LOTTO_MAX_NUMBER, LOTTO_SIZE);
Collections.sort(nums);
return new Lotto(nums);
}

public static List<Lotto> createLottos(int totalAmount) {
List<Lotto> lottos = new ArrayList<>();
for (int i=0; i<totalAmount; i++) {
lottos.add(createRandomLotto());
}

return lottos;
}

public static WinnerLotto createRandomWinnerLotto() {
List<Integer> nums = Randoms.pickUniqueNumbersInRange(LOTTO_MIN_NUMBER, LOTTO_MAX_NUMBER, LOTTO_SIZE+1);

int bonusNumber = nums.getLast();
nums.removeLast();

return new WinnerLotto(nums, bonusNumber);
}

public static WinnerLotto createWinnerLotto(List<Integer> nums, int bonusNumber) {
return new WinnerLotto(nums, bonusNumber);
}

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

import lotto.enums.ErrorMessage;

import java.util.List;

public class InputValidator {
public static void validateInput(String input) {
if (input == null) { // !input.matches("\\d+")
throw new IllegalArgumentException(ErrorMessage.EMPTY_INPUT.getMessage());
}
}
public static int canParseToNumber(String input) {
try {
return Integer.parseInt(input);
} catch (NumberFormatException e) {
throw new IllegalArgumentException(ErrorMessage.NOT_NUMBER.getMessage());
}
}

public static int validateBonusNumber(String input, List<Integer> numbers) {
validateInput(input);
int bonus = canParseToNumber(input);

for (int num : numbers) {
if (bonus == num) {
throw new IllegalArgumentException(ErrorMessage.CAN_NOT_MATCH_BONUS_NUMBER.getMessage());
}
}

return bonus;
}
}
Loading