Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
79 commits
Select commit Hold shift + click to select a range
adf5bbd
feat: 입력 메시지 생성
mgim9316-a11y Mar 29, 2026
48c77a2
feat: Controller 호출
mgim9316-a11y Mar 29, 2026
474aadd
docs: 구현 순서 작성
mgim9316-a11y Mar 29, 2026
b5eb6ff
refactor: 메서드 이름 수정
mgim9316-a11y Mar 29, 2026
381d67b
feat: 구입 금액 작성 기능 추가
mgim9316-a11y Mar 29, 2026
d4c2f13
docs: 생략된 부분 수정
mgim9316-a11y Mar 29, 2026
15b6263
feat: 시도 횟수 관리 클래스 생성
mgim9316-a11y Mar 29, 2026
d5fff4a
feat: 시도 횟수 Controller로 호출 기능 추가
mgim9316-a11y Mar 29, 2026
b0d1d45
chore: 컨벤션 수정
mgim9316-a11y Mar 29, 2026
84eb0a5
feat: 로또 번호를 랜덤으로 작성하는 기능 추가 및 일급 컬렉션으로 관리
mgim9316-a11y Mar 29, 2026
c5de9ef
feat: 랜덤 로또 번호를 출력하는 기능 추가
mgim9316-a11y Mar 29, 2026
a5a6c94
feat: 당첨 번호를 입력 받는 기능 추가
mgim9316-a11y Mar 29, 2026
4200424
feat: 수익률을 계산하는 기능 추가
mgim9316-a11y Mar 29, 2026
96c0a6d
feat: Enum을 통한 도메인 규칙 캡슐화
mgim9316-a11y Mar 29, 2026
1a69d0a
feat: 구매값 반환
mgim9316-a11y Mar 29, 2026
41c7019
feat: 결과 출력 기능 생성
mgim9316-a11y Mar 29, 2026
e37570e
fix: 오류 수정
mgim9316-a11y Mar 29, 2026
bf79e60
feat: 당첨번호 입력 호출, 결과 출력 호출 기능 추가
mgim9316-a11y Mar 29, 2026
ab9294d
refactor: 효율적으로 출력기능 변경
mgim9316-a11y Mar 29, 2026
b2c9729
refactor: 효율적으로 출력기능 변경
mgim9316-a11y Mar 29, 2026
71766eb
refactor: 요구사항 출력 기능 생성
mgim9316-a11y Mar 29, 2026
3b833ed
feat: TrialNumber 검증기능 추가
mgim9316-a11y Mar 30, 2026
1cf15ec
feat: 당첨번호, 구입 금액 검증 기능 추가
mgim9316-a11y Mar 30, 2026
0df50c8
chore: 변수명 변경
mgim9316-a11y Mar 30, 2026
6e69fda
chore: 변수명 변경
mgim9316-a11y Mar 30, 2026
1dc050d
docs: Contoller의 기능 위주로 리드미 수정
mgim9316-a11y Mar 30, 2026
35e644e
chore: 공백 추가
mgim9316-a11y Mar 30, 2026
99e7044
chore: 변수명 수정
mgim9316-a11y Mar 30, 2026
b469635
chore: 변수명 수정
mgim9316-a11y Mar 31, 2026
1bc2243
refactor: 검증 순서 수정
mgim9316-a11y Mar 31, 2026
1bc0835
feat: 잘못 입력시 다시 숫자를 입력 받는 로직 추가
mgim9316-a11y Mar 31, 2026
4429df3
feat: 로또 배열을 생성하는 기능 분리
mgim9316-a11y Mar 31, 2026
33c6110
refactor: depth의 길이 조건 만족
mgim9316-a11y Mar 31, 2026
978b639
refactor: 변수명 변경
mgim9316-a11y Mar 31, 2026
5e9291d
refactor: 로또 생성 기능 분리
mgim9316-a11y Mar 31, 2026
167c8b0
refactor: domain test 코드 작성
mgim9316-a11y Mar 31, 2026
f22c39d
feat: 로또 번호 숫자 범위 확인 기능 추가
mgim9316-a11y Mar 31, 2026
9c7c8f2
refactor: depth1 넘지 않도록 수정
mgim9316-a11y Mar 31, 2026
a41be4e
chore: 공백추가
mgim9316-a11y Mar 31, 2026
e97bb51
chore: 공백추가
mgim9316-a11y Mar 31, 2026
ff87d7b
refactor: 와일드카드 수정, run 메서드 분리, depth1로 생성
mgim9316-a11y Apr 3, 2026
bdccdfe
refactor: depth1로 생성
mgim9316-a11y Apr 3, 2026
06e66e3
refactor: depth1로 생성
mgim9316-a11y Apr 3, 2026
9b880d8
refactor: given 주석 추가
mgim9316-a11y Apr 3, 2026
5db58c1
feat: 테스트 코드 작성
mgim9316-a11y Apr 3, 2026
f1dbc56
feat: 테스트 코드 작성
mgim9316-a11y Apr 3, 2026
40664db
refator: error메시지 생성, depth1 추가
mgim9316-a11y Apr 3, 2026
9182842
chore: 불필요한 주석 삭제
mgim9316-a11y Apr 3, 2026
b0e11d8
chore: 불필요한 주석 삭제
mgim9316-a11y Apr 3, 2026
d50e676
chore: 포멧수정
mgim9316-a11y Apr 3, 2026
cdf6530
chore: 포멧수정
mgim9316-a11y Apr 3, 2026
817ecf1
feat: 보너스 번호 입력 기능 추가
mgim9316-a11y Apr 5, 2026
3b3bdd5
feat: 보너스 번호 입력 기능 추가
mgim9316-a11y Apr 5, 2026
49c6584
feat: 수동으로 로또 번호 입력 기능 추가
mgim9316-a11y Apr 5, 2026
5e3513c
feat: 수동으로 입력 받은 로또 번호 결과 검증 기능 추가
mgim9316-a11y Apr 5, 2026
df71a62
refator:숫자, 숫자 검증 기능 수정
mgim9316-a11y Apr 5, 2026
c31ec9e
feat: 로또 랜덤을 테스트하는 코드 작성
mgim9316-a11y Apr 5, 2026
7ec8baa
feat: 랜덤에 대한 테스트 코드 작성
mgim9316-a11y Apr 5, 2026
602d461
refactor: 전체 구매한 수량에서 수동 로또 구매한 개수를 제외한 수만큼 자동으로 출력
mgim9316-a11y Apr 5, 2026
2a62420
refactor: 값을 하드코딩하지 않는다.
mgim9316-a11y Apr 5, 2026
0e8d69b
refactor: 값을 하드코딩하지 않는다.
mgim9316-a11y Apr 5, 2026
8f6e8c1
refactor: 와일드카드 수정
mgim9316-a11y Apr 5, 2026
38307a5
refactor: 순서 주석 처리
mgim9316-a11y Apr 6, 2026
96aeb0d
fix : 잘못된 출력 형식 수정
mgim9316-a11y Apr 6, 2026
af13a01
feat: 수기 로또 번호, 보너스 번호를 입력 받는 파일 생성
mgim9316-a11y Apr 6, 2026
63e613b
refactor: 로또 번호 validate 호출
mgim9316-a11y Apr 6, 2026
a5fa889
feat: 검증 기능 추가
mgim9316-a11y Apr 6, 2026
3df98b3
docs: 추가 기능에 대한 리드미 작성
mgim9316-a11y Apr 6, 2026
4135454
refactor: 개행 추가
mgim9316-a11y Apr 6, 2026
d7fafe3
refactor: 중복되는 검증값 단일화
mgim9316-a11y Apr 7, 2026
1649b26
refactor: 매직넘버 수정 리터럴 추가
mgim9316-a11y Apr 7, 2026
22d4d73
refactor: 보너스 일치 여부 필드 추가
mgim9316-a11y Apr 7, 2026
5b1ddd0
refactor: 메서드 이름 변경
mgim9316-a11y Apr 7, 2026
cccc8b0
feat: 공통된 이름 validator 추가
mgim9316-a11y Apr 7, 2026
f43ffd4
refactor: 메서드 이름 변경
mgim9316-a11y Apr 7, 2026
ec91681
refactor: 매직넘버 추가
mgim9316-a11y Apr 7, 2026
98ee0ee
refactor: 출력 메서드 변경
mgim9316-a11y Apr 7, 2026
26cc5de
refactor: 테스트코드 수정
mgim9316-a11y Apr 7, 2026
6c39761
chore: 개행추가
mgim9316-a11y Apr 7, 2026
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
15 changes: 15 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
* **[1] 구입 금액 입력 및 시도 횟수 계산**
* 숫자 형식, 1,000원 단위, 0 이하 값 입력 시 `IllegalArgumentException` 발생 및 재시도.
* **[2] 수동 로또 개수 입력 및 번호 생성**
* 총 발행 횟수 초과 및 음수 입력 검증.
* 1~45 범위의 중복되지 않는 6개 숫자 입력 검증.
* **[3] 자동 로또 번호 생성**
* 총 횟수에서 수동 횟수를 제외한 만큼 `RandomLottoNumberGenerator`를 통해 번호 자동 생성 및 오름차순 정렬.
* **[4] 로또 발급 내역 출력**
* 수동 및 자동 발급 수량과 전체 로또 번호 리스트 출력.
* **[5] 당첨 번호 및 보너스 번호 입력**
* 우승 로또 번호 입력 검증.
* 보너스 번호의 범위(1~45) 및 당첨 번호와의 중복 여부 검증.
* **[6] 당첨 결과 계산 및 통계 출력**
* `LottoResult`와 `Rank` Enum을 활용하여 일치 개수 및 상금 계산.
* 당첨 내역 출력 및 수익률(소수점 셋째 자리 내림 처리) 계산 후 출력.
8 changes: 8 additions & 0 deletions src/main/java/Application.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import controller.Controller;

public class Application {
public static void main(String[] args) {
Controller controller = new Controller();
controller.run();
}
}
100 changes: 100 additions & 0 deletions src/main/java/controller/Controller.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
package controller;

import domain.Lotto;
import domain.LottoMachine;
import domain.LottoTickets;
import domain.TrialNumber;
import domain.LottoResult;
import domain.RandomLottoNumberGenerator;
import domain.validator.BonusNumberValidator;
import domain.validator.ManualCountValidator;

import view.InputView;
import view.OutputView;

import java.util.List;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.IntStream;

public class Controller {

public void run() {
TrialNumber trialNumber = getTrialNumber();
int manualTrialCount = getManualTrialCount(trialNumber.getTrialCount());
LottoTickets manualTickets = getManualLottoTickets(manualTrialCount);
int autoTrialCount = trialNumber.getTrialCount() - manualTrialCount;
LottoTickets autoTickets = issueLottoTickets(autoTrialCount, manualTrialCount);
Lotto winningLotto = getWinningLotto();
int bonusNumber = getBonusNumber(winningLotto);
LottoResult statisticsResult = calculateAndPrintResults(autoTickets, winningLotto, bonusNumber, manualTickets);
OutputView.printWinningStatistics(statisticsResult, trialNumber.getPurchaseAmount());
}

private TrialNumber getTrialNumber() {
return retry(() -> {
OutputView.printInputPurchaseAmount();
return new TrialNumber(InputView.inputPurchaseMoney());
});
}

private int getManualTrialCount(int totalTrialCount) {
return retry(() -> {
OutputView.printManualTrialCount();
int manualCount = InputView.inputManualLottoNumberTrialCount();
ManualCountValidator.validate(totalTrialCount, manualCount);
return manualCount;
});
}

private LottoTickets getManualLottoTickets(int trialCount) {
if (trialCount == 0) {
return new LottoTickets(List.of());
}
return retry(() -> {
OutputView.printManualLottoTickets();
List<Lotto> manualLottos = IntStream.range(0, trialCount)
.mapToObj(i -> new Lotto(InputView.inputManualLottoNumber()))
.collect(Collectors.toList());
return new LottoTickets(manualLottos);
});
}

private LottoTickets issueLottoTickets(int trialCount, int manualCount) {
LottoMachine lottoMachine = new LottoMachine(new RandomLottoNumberGenerator());
List<Lotto> generatedLottos = lottoMachine.issue(trialCount);
LottoTickets lottoTickets = new LottoTickets(generatedLottos);

OutputView.printLottoNumber(lottoTickets, trialCount, manualCount);
return lottoTickets;
}

private Lotto getWinningLotto() {
return retry(() -> {
OutputView.printInputWinningNumber();
return new Lotto(InputView.inputWinningNumber());
});
}

private int getBonusNumber(Lotto winningLotto) {
return retry(() -> {
OutputView.printBonusNumber();
int bonusNumber = InputView.inputBonusNumber();
BonusNumberValidator.validate(winningLotto, bonusNumber);
return bonusNumber;
});
}

private LottoResult calculateAndPrintResults(LottoTickets autoTickets, Lotto winningLotto, int bonusNumber, LottoTickets manualTickets) {
return new LottoResult(autoTickets, winningLotto, bonusNumber, manualTickets);
}

private <T> T retry(Supplier<T> supplier) {
try {
return supplier.get();
} catch (IllegalArgumentException e) {
OutputView.printErrorMessage(e.getMessage());
return retry(supplier);
}
}
}
36 changes: 36 additions & 0 deletions src/main/java/domain/Lotto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package domain;

import domain.validator.LottoNumberValidator;

import java.util.Collections;
import java.util.HashSet;
import java.util.List;

public class Lotto {
private static final int LOTTO_SIZE = 6;

private final List<Integer> lottoNumber;

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

private void validate(List<Integer> lottoNumber) {
if (lottoNumber.size() != LOTTO_SIZE) {
throw new IllegalArgumentException("[ERROR] 로또 번호는 6개여야 합니다.");
}
if (new HashSet<>(lottoNumber).size() != LOTTO_SIZE) {
throw new IllegalArgumentException("[ERROR] 로또 번호에 중복된 숫자가 있습니다.");
}
}

private void validateRange(List<Integer> lottoNumber) {
lottoNumber.forEach(LottoNumberValidator::validateRange);
}

public List<Integer> getNumbers() {
return Collections.unmodifiableList(lottoNumber);
}
}
19 changes: 19 additions & 0 deletions src/main/java/domain/LottoMachine.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package domain;

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

public class LottoMachine {
private final LottoNumberGenerator generator;

public LottoMachine(LottoNumberGenerator generator) {
this.generator = generator;
}

public List<Lotto> issue(int trialCount) {
return IntStream.range(0, trialCount)
.mapToObj(i -> new Lotto(generator.generate()))
.collect(Collectors.toList());
}
}
7 changes: 7 additions & 0 deletions src/main/java/domain/LottoNumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package domain;

import java.util.List;

public interface LottoNumberGenerator {
List<Integer> generate();
}
55 changes: 55 additions & 0 deletions src/main/java/domain/LottoResult.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package domain;

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

public class LottoResult {
private final Map<Rank, Integer> matchResults;
private final int bonusNumber;
private static final int INITIAL_COUNT = 0;
private static final int INCREMENT_COUNT = 1;

public LottoResult(LottoTickets lottoTickets, Lotto winningLotto, int bonusNumber, LottoTickets manualLottoTickets) {
this.matchResults = new EnumMap<>(Rank.class);
this.bonusNumber = bonusNumber;
initResults();
calculate(lottoTickets.getLottoNumber(), winningLotto.getNumbers(), this.bonusNumber, manualLottoTickets.getLottoNumber());
}

private void initResults() {
for (Rank rank : Rank.values()) {
matchResults.put(rank, INITIAL_COUNT);
}
}

private void calculate(List<Lotto> lottoNumber, List<Integer> winningNumbers, int bonusNumber, List<Lotto> manullottoNumber) {
lottoNumber.forEach(lotto -> updateMatchResult(lotto, winningNumbers, bonusNumber));
manullottoNumber.forEach(lotto -> updateMatchResult(lotto, winningNumbers, bonusNumber));
}

private void updateMatchResult(Lotto lotto, List<Integer> winningNumbers, int bonusNumber) {
int matchCount = countMatch(lotto.getNumbers(), winningNumbers);
boolean matchBonus = lotto.getNumbers().contains(bonusNumber);
Rank rank = Rank.valueOfRank(matchCount, matchBonus);
matchResults.put(rank, matchResults.get(rank) + INCREMENT_COUNT);
}

private int countMatch(List<Integer> lottoNumber, List<Integer> winningNumbers) {
return (int) lottoNumber.stream()
.filter(winningNumbers::contains)
.count();
}

public double calculateProfitRate(int purchaseAmount) {
long totalPrize = 0L;
for (Map.Entry<Rank, Integer> entry : matchResults.entrySet()) {
totalPrize += (long) entry.getKey().getPrizeMoney() * entry.getValue();
}
return (double) totalPrize / purchaseAmount;
}

public int getRankCount(Rank rank) {
return matchResults.get(rank);
}
}
16 changes: 16 additions & 0 deletions src/main/java/domain/LottoTickets.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package domain;

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

public class LottoTickets {
private final List<Lotto> lottoTickets;

public LottoTickets(List<Lotto> lottoTickets) {
this.lottoTickets = lottoTickets;
}

public List<Lotto> getLottoNumber() {
return Collections.unmodifiableList(lottoTickets);
}
}
30 changes: 30 additions & 0 deletions src/main/java/domain/RandomLottoNumberGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package domain;

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

public class RandomLottoNumberGenerator implements LottoNumberGenerator {


private static final int MIN_LOTTO_NUMBER = 1;
private static final int MAX_LOTTO_NUMBER = 45;
private static final int LOTTO_NUMBER_COUNT = 6;

private final List<Integer> lottoPool;

public RandomLottoNumberGenerator() {
this.lottoPool = new ArrayList<>();
for (int i = MIN_LOTTO_NUMBER; i <= MAX_LOTTO_NUMBER; i++) {
lottoPool.add(i);
}
}

@Override
public List<Integer> generate() {
Collections.shuffle(lottoPool);
List<Integer> selectedNumbers = new ArrayList<>(lottoPool.subList(0, LOTTO_NUMBER_COUNT));
Collections.sort(selectedNumbers);
return selectedNumbers;
}
}
55 changes: 55 additions & 0 deletions src/main/java/domain/Rank.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
package domain;

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

public enum Rank {
NONE(0, false, 0),
THREE(3, false, 5000),
FOUR(4, false, 50000),
FIVE(5, false, 1500000),
FIVE_BONUS(5, true, 30000000),
SIX(6, false, 2000000000);

private final int matchCount;
private final boolean matchBonus;
private final int prizeMoney;

Rank(int matchCount, boolean matchBonus, int prizeMoney) {
this.matchCount = matchCount;
this.matchBonus = matchBonus;
this.prizeMoney = prizeMoney;
}

public static Rank valueOfRank(int matchCount, boolean matchBonus) {
return Arrays.stream(values())
.filter(rank -> rank.isMatch(matchCount, matchBonus))
.findFirst()
.orElse(NONE);
}

private boolean isMatch(int matchCount, boolean matchBonus) {
if (this.matchCount != matchCount) {
return false;
}
if (this == FIVE_BONUS || this == FIVE) {
return this.matchBonus == matchBonus;
}
return true;
}

public static List<Rank> getWinningRanks() {
return Arrays.stream(values())
.filter(rank -> rank != NONE)
.collect(Collectors.toList());
}

public int getPrizeMoney() {
return prizeMoney;
}

public int getMatchCount() {
return matchCount;
}
}
31 changes: 31 additions & 0 deletions src/main/java/domain/TrialNumber.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
package domain;

public class TrialNumber {
private static final int LOTTO_PRICE = 1000;
private static final int ZERO = 0;
private final int trialCount;
private final int purchaseAmount;

public TrialNumber(int purchaseAmount) {
validateAmount(purchaseAmount);
this.purchaseAmount = purchaseAmount;
this.trialCount = purchaseAmount / LOTTO_PRICE;
}

private void validateAmount(int amount) {
if (amount <= ZERO) {
throw new IllegalArgumentException("[ERROR] 구입 금액은 0보다 커야 합니다.");
}
if (amount % LOTTO_PRICE != ZERO) {
throw new IllegalArgumentException("[ERROR] 구입 금액은 1000원 단위여야 합니다.");
}
}

public int getTrialCount() {
return trialCount;
}

public int getPurchaseAmount() {
return purchaseAmount;
}
}
13 changes: 13 additions & 0 deletions src/main/java/domain/validator/BonusNumberValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package domain.validator;

import domain.Lotto;

public class BonusNumberValidator {

public static void validate(Lotto winningLotto, int bonusNumber) {
LottoNumberValidator.validateRange(bonusNumber);
if (winningLotto.getNumbers().contains(bonusNumber)) {
throw new IllegalArgumentException("[ERROR] 보너스 번호는 당첨 번호와 중복될 수 없습니다.");
}
}
}
Loading