Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
42 commits
Select commit Hold shift + click to select a range
8ba883a
feat(view) : making view
Sep 24, 2025
8ae5422
feat(model) : making Model
Sep 24, 2025
8dd8277
feat(controller) : making controller
Sep 24, 2025
4c65cb7
feat(application) : making Application
Sep 24, 2025
128dee6
refactor(view) : 'OutView'
Sep 25, 2025
daa8759
refactor(model,controller) : adapting 'OutView'
Sep 25, 2025
28b7703
refactor(All) : clean up code line
Sep 25, 2025
f63efd3
feat : READ.ME
Sep 28, 2025
52ef26b
refacting : clean line
Sep 28, 2025
af6c2f5
refacting : renaming "input" , "output"
Sep 28, 2025
dfb1e77
feat : enum "PrizeMoney"
Sep 28, 2025
bcdad0a
refactoring : using enum "PrizeMoney"
Sep 28, 2025
ab6a0ee
refactoring(inputview) : exception handling
Sep 28, 2025
201caa7
refactoring(model) : magic number "COST_PER_TICKET"
Sep 28, 2025
04a10e1
refactoring(model) : renaming constructor parameter "inputJackpot"
Sep 28, 2025
165964f
refactoring : in getter, using only read method
Sep 28, 2025
70d4cbb
refactoring(controller) : renaming "intputView","outputView
Sep 29, 2025
68ce019
refactoring(model) : "COST_PER_TICKET"
Sep 29, 2025
1e7dc53
refactoring(model) : fixing rogic of "validate"
Sep 29, 2025
793e9a3
refactoring(inputView) : adding "nextLine()" to break out of infinite…
Sep 29, 2025
2358d22
feat(model) : class "NumberGenerate"
Oct 1, 2025
29060b4
feat(model) : class "AutoLotto"
Oct 1, 2025
a472ddf
feat(model) : class "ManualLotto"
Oct 1, 2025
1bc5b7d
refactor(view) : class "OutView","InputView"
Oct 1, 2025
bb86d4f
refactor(view) : field "Bonus"
Oct 1, 2025
4659d1a
refactor(model) : Implement auto/manual lottery number generation logic
Oct 1, 2025
46091b5
refactor(model) : adding bonus ball calculation
Oct 1, 2025
53d1b38
refactor(model) : field "bonusNumber"
Oct 1, 2025
28ce4b9
refactor(model) : use inherited parent object
Oct 1, 2025
ee3d545
refactor(model) : - Apply updates from latest changes
Oct 1, 2025
517dcee
delete(model) : NumberGenerate.java
Oct 1, 2025
a8751ae
refactor(READ.ME)
Oct 1, 2025
6cc626c
refactor(model) : adding validate rogic
Oct 1, 2025
43b220a
refactor(view) : using System.err.print
Oct 4, 2025
418292f
refactor : unify variable names for consistency
Oct 5, 2025
166a97a
refactor : unify "iterable" interface
Oct 5, 2025
fbcd282
refactor : validate rogic(ManualLotto)
Oct 5, 2025
52e393b
refactor : using TreeSet
Oct 7, 2025
dae1be8
refactor : move print method from inputView to outputView
Oct 7, 2025
c341fa7
refactor : ManualLotto validate rogic
Oct 7, 2025
acc8b99
refactor : get and check ManualLotto one by one
Oct 8, 2025
36937b0
refactor : using return value of method 'add'
Oct 8, 2025
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
66 changes: 66 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
# Java Lotto Clean Playground

## 📖 개요

## ✨ 주요 기능

- **로또 구매**: 입력한 금액에 맞춰 1,000원 단위로 로또를 구매합니다.
- **수동/자동 구매**: 원하는 개수만큼 로또 번호를 직접 선택하는 수동 구매와 나머지 개수를 자동으로 발급받는 기능을 지원합니다.
- **구매 내역 출력**: 구매한 모든 로또(수동, 자동)의 번호를 출력합니다.
- **당첨 통계**: 사용자가 입력한 당첨 번호 및 보너스 번호를 기반으로 당첨 내역을 계산합니다.
- 1등: 6개 번호 일치
- 2등: 5개 번호 + 보너스 번호 일치
- 3등: 5개 번호 일치
- 4등: 4개 번호 일치
- 5등: 3개 번호 일치
- **수익률 계산**: 총 구매 금액 대비 당첨금 총액을 기반으로 한 수익률을 소수점 둘째 자리까지 계산하여 출력합니다.

```
.
└── src
└── main
└── java
├── Application.java # 애플리케이션 시작점
├── Controller
│ └── LottoController.java # 게임 흐름 제어
├── Model
│ ├── Lotto.java # 로또 1장
│ ├── Lottos.java # 발급된 전체 로또
│ ├── Jackpot.java # 당첨 번호와 보너스 번호
│ ├── LottoMatcher.java # 로또 당첨 여부 확인 및 통계 계산
│ ├── PrizeMoney.java # 등수별 상금 Enum
│ ├── AutoLotto.java # 자동 로또 번호 생성
│ └── ManualLotto.java # 수동 로또 번호 관리
└── View
├── InputView.java # 사용자 입력 처리
└── OutView.java # 결과 출력 담당

```

## ⚙️ 핵심 로직 상세

### 1. 로또 생성 (`Lottos.java`)

- `Lottos` 클래스는 사용자가 구매한 모든 로또 티켓을 관리합니다.
- `InputView`를 통해 받은 수동 구매 개수와 번호 문자열 리스트를 `ManualLotto`를 통해 `Lotto` 객체 리스트로 변환합니다.
- 총 구매 개수에서 수동 구매 개수를 뺀 만큼 `AutoLotto`를 통해 자동으로 로또 번호를 생성합니다.
- `AutoLotto`는 `Collections.shuffle`을 활용하여 1부터 45까지의 숫자 중 6개를 무작위로 선택하여 중복 없는 번호를 생성합니다.

### 2. 당첨 확인 (`LottoMatcher.java`)

`LottoMatcher`는 로또 게임의 당첨 결과를 판별하고 통계를 내는, 가장 중요한 비즈니스 로직을 담당하는 클래스입니다. 객체가 생성되는 시점에 모든 핵심 연산을 수행하여 내부에 결과를 저장하고, 외부에는 요청에 따라 계산된 결과를 즉시 제공하는 방식으로 설계되었습니다.

#### 동작 원리

1. **생성 및 초기화**: `LottoController`가 `new LottoMatcher(lottos, jackpot)` 코드로 객체를 생성하면, `LottoMatcher`의 생성자가 실행되며 모든 계산이 즉시 이루어집니다.
- **데이터 준비**: 생성자는 사용자가 구매한 모든 로또 티켓 묶음(`lottos`)과 당첨 번호 정보(`jackpot`)를 인자로 받습니다. 그리고 당첨 통계를 저장할 `matchCounts`라는 `Map`을 초기화합니다. 이 맵은 `{일치 개수: 당첨된 티켓 수}` 형태로 통계를 저장하게 됩니다.
- **개별 티켓 비교**: `for` 루프를 통해 구매한 로또 티켓을 한 장씩 순회하며 각 티켓별로 당첨 등수를 판별합니다.
- 먼저 해당 티켓의 번호 6개가 당첨 번호 6개와 몇 개나 일치하는지 계산합니다.
- 만약 일치 개수가 정확히 **5개**라면, 2등 당첨 가능성이 생깁니다. 이때, 해당 로또 티켓이 **보너스 번호를 포함하고 있는지** 추가로 확인하여 2등과 3등을 구분합니다.
- 2등(5개 일치 + 보너스 번호 포함)으로 확인되면, `matchCounts` 맵에 `7`이라는 특별한 키값으로 당첨 횟수를 1 증가시킵니다.
- 그 외의 경우(3, 4, 5, 6개 일치), 계산된 일치 개수를 키로 사용하여 `matchCounts` 맵의 카운트를 1 증가시킵니다.

2. **결과 제공**: 모든 계산이 완료된 `LottoMatcher` 객체는 `LottoController`의 요청에 따라 아래 메소드들을 통해 결과를 제공합니다.
- `getMatchCounts()`: 모든 계산이 완료된 `matchCounts` 맵을 반환합니다. `OutView`는 이 맵을 받아 최종 당첨 통계를 화면에 출력합니다.
- `calculateTotalEarnings()`: `matchCounts` 맵과 `PrizeMoney` Enum을 기반으로 총상금을 계산합니다.
- `getRate()`: `calculateTotalEarnings()`로 계산된 총상금을 사용자의 총 구매 금액으로 나누어 최종 수익률을 계산합니다.
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.LottoController;

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

import Model.*;
import View.InputView;
import View.OutView;

import java.util.List;

public class LottoController {
public void startLotto() {
InputView inputView = new InputView();
OutView outView = new OutView();


int money = getValidMoney(inputView, outView);

outView.getManualLottoCountHelp();
int manualLottoCount = inputView.getManualLottoCount();

outView.promptForManualLotto();
Lottos lottos = new Lottos();

inputView.resetBuffer();
for (int i = 0; i < manualLottoCount; i++) {
Lotto validManualLotto = getValidManualLotto(inputView);
lottos.addLotto(validManualLotto);
}

int autoLottoCount = lottos.getAutoLottoCount(money, manualLottoCount);
lottos.makeAutoLotto(autoLottoCount);

outView.printLottosStaus(autoLottoCount, manualLottoCount);

outView.printLottos(lottos);

outView.promptForJackpot();
String[] winningNumber = inputView.getJackpotNumber();

outView.promptForBonus();
int bonusNumber = inputView.getBonus();

try {
Jackpot jackpot = new Jackpot(winningNumber, bonusNumber);

LottoMatcher lottoMatcher = new LottoMatcher(lottos, jackpot);

outView.printLottoMatcher(lottoMatcher.getMatchCounts(), lottoMatcher.getRate(money));
} catch (IllegalArgumentException e) {
System.err.println("[ERROR] " + e.getMessage());
}

}

private int getValidMoney(InputView inputView, OutView outView) {
while (true) {
try {
outView.getMoneyHelp();
int money = inputView.getMoney();
validate(money);
return money;
} catch (IllegalArgumentException e) {
System.err.println("[ERROR] " + e.getMessage());
}
}
}

private Lotto getValidManualLotto(InputView inputView){
while(true){
try{
return new ManualLotto(inputView.manualLotto());
} catch (IllegalArgumentException e) {
System.err.println("[ERROR] " + e.getMessage());
}
}
}

private void validate(int money) {
if (money < 0)
throw new IllegalArgumentException("금액이 0보다 작을 순 없습니다!");
if (money % 1000 != 0)
throw new IllegalArgumentException("1000원 단위로 입력해주세요!");
}
}
22 changes: 22 additions & 0 deletions src/main/java/Model/AutoLotto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
package Model;

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

public class AutoLotto extends Lotto {

public AutoLotto() {
List<Integer> numbers = new ArrayList<>();

for (int i = 1; i <= 45; i++) {
numbers.add(i);
}

Collections.shuffle(numbers);
this.numbers = new ArrayList<>(numbers.subList(0, 6));

Collections.sort(this.numbers);
}

}
38 changes: 38 additions & 0 deletions src/main/java/Model/Jackpot.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package Model;

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

public class Jackpot {
private List<Integer> jackpot;
private int bounsNumber;

public Jackpot(String[] inputJackpot, int bounsNumber) {
JackpotGenerator from = JackpotGenerator.from(inputJackpot);
Copy link

Choose a reason for hiding this comment

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

Jackpot 생성작업을 Jackpot 안에 두지 않고 Generator로 분리하신 이유가 궁금해요!

Copy link
Author

Choose a reason for hiding this comment

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

jackpot => jackpotgenerator + bounsNumber 를 관리하는 클래스
jackpotgenerator => 사용자로 부터 정답 로또를 입력받는 클래스

jackpot은 보너스 넘버까지 같이 관리하려고 나눴습니다!

Copy link

Choose a reason for hiding this comment

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

ManualLotto의 경우에는 숫자 검증, 중복 숫자 검증과 같이 객체를 만드는 데에 필요한 검증 작업과 숫자 생성 작업이 생성자 안에 들어가 있습니다.
반면 Jackpot은 생성자에서 진행하지 않고 검증 작업과 숫자 생성 작업을 Generator로 분리하셨지요. 두 객체의 생성 방법에 차이를 두신 이유가 궁금한 것입니다!

Copy link

@BackFoxx BackFoxx Oct 8, 2025

Choose a reason for hiding this comment

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

🔔 요거 답을 안 주셨어요!

Copy link
Author

Choose a reason for hiding this comment

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

아아 죄송합니다!
다른 의미는 크게 없었고 정적팩토리 메소드를 한번 코드에 적용시켜 보고 싶어서 코드를 generator로 구현해봤습니다!

this.jackpot = from.getNumber();
validateBonusNumber(bounsNumber, this.jackpot);
this.bounsNumber = bounsNumber;
}

private void validateBonusNumber(int bounsNumber, List<Integer> jackpot) {
if (bounsNumber < 1 || bounsNumber > 45) {
throw new IllegalArgumentException("보너스 번호는 1과 45 사이의 숫자여야 합니다.");
}
if (jackpot.contains(bounsNumber)) {
throw new IllegalArgumentException("보너스 번호는 당첨 번호와 중복될 수 없습니다.");
}
}

public List<Integer> getJackpot() {
return Collections.unmodifiableList(jackpot);
}

public int getBounsNumber() {
return bounsNumber;
}

@Override
public String toString() {
return jackpot.toString();
}
}
50 changes: 50 additions & 0 deletions src/main/java/Model/JackpotGenerator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package Model;

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

public class JackpotGenerator {
private final List<Integer> number;

private JackpotGenerator(List<Integer> numbers) {
this.number = numbers;
}

public static JackpotGenerator from(String[] splits) {
Set<Integer> numbers = parseAndValidateNumbers(splits);
return new JackpotGenerator(Collections.unmodifiableList(new ArrayList<>(numbers)));
}

private static Set<Integer> parseAndValidateNumbers(String[] splits) {
if (splits.length != 6) {
throw new IllegalArgumentException("당첨 번호는 6개를 입력해야 합니다.");
}
Set<Integer> numbers = new TreeSet<>();
for (String s : splits) {
int number;
try {
number = Integer.parseInt(s.trim());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("당첨 번호는 숫자여야 합니다.");
}
validateNumberRange(number);
if (!numbers.add(number)) {
throw new IllegalArgumentException("당첨 번호는 중복될 수 없습니다.");
}
}
return numbers;
}

private static void validateNumberRange(int number) {
if (number < 1 || number > 45) {
throw new IllegalArgumentException("당첨 번호는 1과 45 사이의 숫자여야 합니다.");
}
}

public List<Integer> getNumber() {
return number;
}
}
17 changes: 17 additions & 0 deletions src/main/java/Model/Lotto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package Model;

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

public class Lotto {
protected List<Integer> numbers;

public List<Integer> getLotto() {
return Collections.unmodifiableList(numbers);
}

@Override
public String toString() {
return numbers.toString();
}
}
61 changes: 61 additions & 0 deletions src/main/java/Model/LottoMatcher.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
package Model;

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

public class LottoMatcher {
private final Map<Integer, Integer> matchCounts;

public LottoMatcher(Lottos lottos, Jackpot jackpot) {
this.matchCounts = new HashMap<>();

for (Lotto lotto : lottos) {
int matches = countMatches(jackpot.getJackpot(), lotto.getLotto());
Copy link

Choose a reason for hiding this comment

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

LottoMatcher의 역할이 lottos의 번호들과 Jackpot 번호를 대조해보는 것이라면, 이 작업 전체를 그냥 Lottos 객체 안에서 수행해도 되지 않을까요? Matcher가 꼭 분리되어 존재해야 하는 이유가 궁금합니다.

Copy link
Author

Choose a reason for hiding this comment

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

과제 요구사항에서 하나의 클래스 안에선 메소드가 10개 이하를 유지하기 위해서 클래스를 분리해서 만들어보았습니다!! 또한 기능 상 Lottos가 정답과 비교하는 기능 까지 가지고 있으면 너무 많은 책임을 가지고 있는 것 같아서 matcher라는 클래스를 생성해보았습니다!

checkBonus(jackpot, lotto, matches);
}

}

private void checkBonus(Jackpot jackpot, Lotto lotto, int matches) {
if (matches == 5 && lotto.getLotto().contains(jackpot.getBounsNumber())) {
matchCounts.put(7, matchCounts.getOrDefault(7, 0) + 1);
return;
}
matchCounts.put(matches, matchCounts.getOrDefault(matches, 0) + 1);
}

private int countMatches(List<Integer> jackpotNumbers, List<Integer> lottoNumbers) {
int count = 0;
for (int number : lottoNumbers) {
count += getMatchValue(jackpotNumbers, number);
}
return count;
}


private int getMatchValue(List<Integer> jackpotNumbers, int number) {
if (jackpotNumbers.contains(number)) {
return 1;
}
return 0;
}

public Map<Integer, Integer> getMatchCounts() {
return matchCounts;
}

public double calculateTotalEarnings() {

return matchCounts.getOrDefault(3, 0) * PrizeMoney.THREE.getMoney()
+ matchCounts.getOrDefault(4, 0) * PrizeMoney.FOUR.getMoney()
+ matchCounts.getOrDefault(5, 0) * PrizeMoney.FIVE.getMoney()
+ matchCounts.getOrDefault(7, 0) * PrizeMoney.BONUS.getMoney()
+ matchCounts.getOrDefault(6, 0) * PrizeMoney.SIX.getMoney();
}

public double getRate(int money) {
return calculateTotalEarnings() / money;
}

}
35 changes: 35 additions & 0 deletions src/main/java/Model/Lottos.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
package Model;

import java.util.*;
import java.util.function.Consumer;

// 1. 제네릭을 사용하여 Iterable<Lotto>로 타입 명시
public class Lottos implements Iterable<Lotto> {
private List<Lotto> lottoList;
private static final int COST_PER_TICKET = 1000;

public Lottos() {
this.lottoList = new ArrayList<>();
}


public void addLotto (Lotto tmp){
lottoList.add(tmp);
}

public int getAutoLottoCount(int money,int manualLottosNumber) {
return money / COST_PER_TICKET - manualLottosNumber;
}

public void makeAutoLotto(int autoLottosNumber){
for (int i = 0; i < autoLottosNumber; i++) {
AutoLotto autoLotto = new AutoLotto();
lottoList.add(autoLotto);
}
}

@Override
public Iterator<Lotto> iterator() {
return lottoList.iterator();
}
}
Loading