Skip to content
53 changes: 53 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
# 기능 목록
+ 각 기능에 대한 예외사항과 핸들링도 기재
+ MVC 패턴을 사용

- model 패키지
- [x] 로또 번호 관리 - Lotto class
- [x] 특정 번호가 로또 번호 리스트에 포함되어 있는지 확인 - contains()
- [x] 당첨 번호 리스트와 로또 번호 리스트의 일치 개수를 반환 - matchCount()
- [x] 로또 번호 리스트 반환 - getNumbers()
- [x] 로또 당첨 등수 관리 - LottoRank class
- [x] 일치하는 등수를 찾음 - findMatchingRank()
- [x] 등수의 설명을 반환 - getDescription()
- [x] 등수의 상금을 반환 - getPrize()
- [x] 로또 당첨 결과 관리 - LottoResult class
- [x] 당첨 결과를 추가 - addResult()
- [x] 전체 상금 합계 반환 - calculateTotalPrize()
- [x] 랭크 설명을 역순으로 반환 - reverseRankDescriptions()
- [x] 각 랭크의 당첨 개수를 역순으로 반환 - reverseRankCounts()
- view 패키지
- [x] 사용자 입력 처리 - LottoInputView class
- [x] 로또 구입 금액 입력 받기 - receiveUserMoneyInput()
- 예외사항 : 구입 금액이 1,000원 단위가 아닌 경우 ➔ IllegalArgumentException
- 예외사항 : 입력이 숫자가 아닌 경우 ➔ IllegalArgumentException
- [x] 당첨 번호 입력 받기 - receiveWinningNumberInput()
- 예외사항 : 당첨번호가 6개가 아닌 경우 ➔ IllegalArgumentException
- 예외사항 : 당첨번호에 중복된 숫자가 있는 경우 ➔ IllegalArgumentException
- 예외사항 : 로또번호가 1부터 45 사이가 아닌 경우 ➔ IllegalArgumentException
- [x] 보너스 번호 입력 받기 - receiveBonusNumberInput()
- 예외사항 : 당첨번호에 중복된 숫자가 있는 경우 ➔ IllegalArgumentException
- 예외사항 : 로또번호가 1부터 45 사이가 아닌 경우 ➔ IllegalArgumentException
- [x] 결과 출력 - LottoOutputView class
- [x] 로또 번호 목록 출력 - printLotto()
- [x] 당첨 통계 출력 - printWinningStatistics()
- [x] 총 수익률 출력 - printProfitRate()
- controller 패키지
- [x] 로또게임 구현 - GameController class
- [x] 게임 시작 - start()
- [x] 로또 게임 로직 처리 - LottoController class
- [x] 로또 번호 생성 - generateLottoNumber()
- [x] 당첨 결과 확인 - checkWinningResult()
- [x] 수익률 계산 - calculateProfitRate()
- util 패키지
- [x] 입력 변환 유틸리티 - LottoParser class
- [x] 입력 정수 리스트로 변환 - parseNumbers()
- [x] 입력 검증 유틸리티 - LottoValidator class
- [x] 구입 금액 검증 - validateMoneyInput()
- [x] 로또 번호 리스트 검증 - validateNumbers()
- [x] 보너스 번호 검증 - validateBonusNumber()

-------

* 당첨 번호와 보너스 번호를 입력하는 기능을 분리하는게 좋을까?
* 가독성을 위해 분리하는 방향으로 선택
5 changes: 4 additions & 1 deletion src/main/java/lotto/Application.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
package lotto;

import lotto.controller.GameController;

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
GameController gameController = new GameController();
gameController.start();
}
}
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/GameController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package lotto.controller;

import lotto.view.LottoInputView;
import lotto.view.LottoOutputView;
import lotto.model.Lotto;
import lotto.model.LottoResult;

import java.util.List;
import java.util.stream.Collectors;

public class GameController {
private final LottoInputView lottoInputView;
private final LottoController lottoService;
private final LottoOutputView lottoOutputView;
private static final int LOTTO_PRICE = 1000;

public GameController() {
this.lottoService = new LottoController();
this.lottoOutputView = new LottoOutputView();
this.lottoInputView = new LottoInputView();
}

/**
* 게임 시작
*/
public void start() {
int money = lottoInputView.receiveUserMoneyInput();
List<Lotto> lottoList = lottoService.generateLottoNumber(money / LOTTO_PRICE);
lottoOutputView.printLotto(lottoList.size(), lottoList.stream()
.map(Lotto::getNumbers)
.collect(Collectors.toList()));

List<Integer> winningNumbers = lottoInputView.receiveWinningNumberInput();
int bonusNumber = lottoInputView.receiveBonusNumberInput(winningNumbers);

LottoResult result = lottoService.checkWinningResult(lottoList, winningNumbers, bonusNumber);
lottoOutputView.printWinningStatistics(result.getRankDescriptions(), result.getRankCounts());

double profitRate = lottoService.calculateProfitRate(money, result);
lottoOutputView.printProfitRate(profitRate);
}
}
50 changes: 50 additions & 0 deletions src/main/java/lotto/controller/LottoController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package lotto.controller;

import camp.nextstep.edu.missionutils.Randoms;
import lotto.model.Lotto;
import lotto.model.LottoRank;
import lotto.model.LottoResult;

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

public class LottoController {

private static final int LOTTO_MIN_NUMBER = 1; // 로또 번호의 최소값
private static final int LOTTO_MAX_NUMBER = 45; // 로또 번호의 최대값
private static final int LOTTO_NUMBER_COUNT = 6; // 로또 번호의 개수
private static final double PERCENTAGE_CONVERSION = 100.0; // 수익률 계산 비율

/**
* 로또 번호 생성
*/
public List<Lotto> generateLottoNumber(int count) {
List<Lotto> result = new ArrayList<>();
for(int i = 0; i < count; i++) {
List<Integer> numbers = Randoms.pickUniqueNumbersInRange(LOTTO_MIN_NUMBER, LOTTO_MAX_NUMBER, LOTTO_NUMBER_COUNT);
result.add(new Lotto(numbers));
}
return result;
}

/**
* 당첨 결과 확인
*/
public LottoResult checkWinningResult(List<Lotto> lottoList, List<Integer> winningNumbers, int bonusNumber) {
LottoResult result = new LottoResult();
for (Lotto lotto : lottoList) {
int matchCount = lotto.matchCount(winningNumbers);
boolean bonusMatch = lotto.contains(bonusNumber);
result.addResult(LottoRank.findMatchingRank(matchCount, bonusMatch));
}
return result;
}

/**
* 수익률 계산
*/
public double calculateProfitRate(int money, LottoResult result) {
long totalPrize = result.getTotalPrize();
return (totalPrize * PERCENTAGE_CONVERSION) / money;
}
}
35 changes: 35 additions & 0 deletions src/main/java/lotto/model/Lotto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package lotto.model;

import java.util.List;

import static lotto.util.LottoValidator.validateNumbers;

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

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

/**
* 특정 번호가 로또 번호 리스트에 포함되어 있는지 확인
*/
public boolean contains(int number) {
return numbers.contains(number);
}

/**
* 당첨 번호 리스트와 로또 번호 리스트의 일치 개수를 반환
*/
public int matchCount(List<Integer> winningNumbers) {
return (int) numbers.stream().filter(winningNumbers::contains).count(); //numbers에 있는 int를 loop 돌려서 winningNumbers에 포함되어있다면 개수를 셈
}

/**
* 로또 번호 리스트 반환
*/
public List<Integer> getNumbers() {
return numbers;
}
}
38 changes: 38 additions & 0 deletions src/main/java/lotto/model/LottoRank.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package lotto.model;

import java.util.Arrays;

public enum LottoRank {
FIRST(6, 2_000_000_000, "6개 일치 (2,000,000,000원)"),
SECOND(5, 30_000_000, "5개 일치, 보너스 볼 일치 (30,000,000원)"),
THIRD(5, 1_500_000, "5개 일치 (1,500,000원)"),
FOURTH(4, 50_000, "4개 일치 (50,000원)"),
FIFTH(3, 5_000, "3개 일치 (5,000원)"),
NONE(0, 0, "");

private final int matchCount;
private final int prize;
private final String description;

LottoRank(int matchCount, int prize, String description) {
this.matchCount = matchCount;
this.prize = prize;
this.description = description;
}

public static LottoRank findMatchingRank(int count, boolean bonusMatch) {
return Arrays.stream(values()) //모든 LottoRank 값을 스트림으로 변환
.filter(rank -> rank.matchCount == count)
.filter(rank -> rank != SECOND || (rank == SECOND && bonusMatch))
.findFirst()
.orElse(NONE);
}

public String getDescription() {
return description;
}

public long getPrize() {
return prize;
}
}
62 changes: 62 additions & 0 deletions src/main/java/lotto/model/LottoResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package lotto.model;

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

public class LottoResult {
private final HashMap<LottoRank, Integer> rankCounts;

public LottoResult() {
this.rankCounts = new HashMap<>();
for (LottoRank lottoRank : LottoRank.values()) {
rankCounts.put(lottoRank, 0);
}
}

/**
* 당첨 결과를 추가
*/
public void addResult(LottoRank rank) {
rankCounts.put(rank, rankCounts.get(rank) + 1); //현재 랭크 갯수보다 하나 업
}

/**
* 특정 랭크의 당첨 개수 반환
*/
public int getCountForRank(LottoRank lottoRank) {
return rankCounts.get(lottoRank);
}

/**
* 전체 상금 합계 반환
*/
public long getTotalPrize() {
return rankCounts.entrySet().stream()
.mapToLong(entry -> entry.getKey().getPrize() * entry.getValue())
.sum();
}

/**
* 랭크 설명을 역순으로 반환
*/
public List<String> getRankDescriptions() {
return rankCounts.keySet().stream()
.filter(rank -> rank != LottoRank.NONE) // NONE 랭크 제외
.sorted((r1, r2) -> r2.ordinal() - r1.ordinal()) // 역순 정렬 (ordinal 값을 기준으로)
.map(LottoRank::getDescription) // 각 랭크의 설명 문자열 추출
.collect(Collectors.toList());
}

/**
* 각 랭크의 당첨 개수를 역순으로 반환
*/
public List<Integer> getRankCounts() {
return rankCounts.entrySet().stream()
.filter(entry -> entry.getKey() != LottoRank.NONE) // NONE 랭크 제외
.sorted((entry1, entry2) -> entry2.getKey().ordinal() - entry1.getKey().ordinal()) // 역순 정렬 (ordinal 값을 기준으로)
.map(Map.Entry::getValue) // 각 랭크의 개수만 추출
.collect(Collectors.toList());
}
}
22 changes: 22 additions & 0 deletions src/main/java/lotto/util/LottoParser.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package lotto.util;

import java.util.Arrays;
import java.util.List;
import java.util.stream.Collectors;

public class LottoParser {


private LottoParser() {
}

/**
* 입력 정수 리스트로 변환
*/
public static List<Integer> parseNumbers(String input) {
return Arrays.stream(input.split(","))
.map(String::trim) //스페이스 제거
.map(Integer::parseInt) //정수 변환
.collect(Collectors.toList());
}
}
56 changes: 56 additions & 0 deletions src/main/java/lotto/util/LottoValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package lotto.util;

import java.util.List;

public class LottoValidator {

private static final int LOTTO_MIN_NUMBER = 1; // 로또 번호의 최소값
private static final int LOTTO_MAX_NUMBER = 45; // 로또 번호의 최대값
private static final int LOTTO_NUMBER_COUNT = 6; // 로또 번호의 개수
private static final int LOTTO_PRICE_UNIT = 1000; // 로또 구입 금액 단위

private LottoValidator() {
// 객체 생성 방지
}

/**
* 구입 금액 검증
*/
public static void validateMoneyInput(String input) {
try {
int money = Integer.parseInt(input);
if (money % LOTTO_PRICE_UNIT != 0) {
throw new IllegalArgumentException("[ERROR] 구입 금액은 1,000원 단위여야 합니다.");
}
} catch (NumberFormatException e) { //숫자가 아닐 경우
throw new IllegalArgumentException("[ERROR] 올바른 숫자를 입력하세요.");
}
}

/**
* 로또 번호 리스트 검증
*/
public static void validateNumbers(List<Integer> numbers) {
if (numbers.size() != LOTTO_NUMBER_COUNT) {
throw new IllegalArgumentException("[ERROR] 당첨 번호는 6개여야 합니다.");
}
if (numbers.stream().distinct().count() != LOTTO_NUMBER_COUNT) {
throw new IllegalArgumentException("[ERROR] 당첨 번호에 중복된 숫자가 있습니다.");
}
if (numbers.stream().anyMatch(n -> n < LOTTO_MIN_NUMBER || n > LOTTO_MAX_NUMBER)) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.");
}
}

/**
* 보너스 번호 검증
*/
public static void validateBonusNumber(int bonusNumber, List<Integer> winningList) {
if (bonusNumber < LOTTO_MIN_NUMBER || bonusNumber > LOTTO_MAX_NUMBER) {
throw new IllegalArgumentException("[ERROR] 보너스 번호는 1부터 45 사이의 숫자여야 합니다.");
}
if (winningList.contains(bonusNumber)) {
throw new IllegalArgumentException("[ERROR] 당첨 번호에 중복된 숫자가 있습니다.");
}
}
}
Loading