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
66 changes: 66 additions & 0 deletions .github/workflows/ci-with-analysis.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
name: CI with Failure Analysis

on:
push:
branches: [main, master]
pull_request:

jobs:
build:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- name: Set up JDK 21
uses: actions/setup-java@v4
with:
java-version: '21'
distribution: 'temurin'

- name: Grant execute permission for gradlew
run: chmod +x gradlew

- name: Build & Test
id: test
continue-on-error: true
run: ./gradlew test

- name: Analyze Failure
if: steps.test.outcome == 'failure'
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
# Python 설치
sudo apt-get update && sudo apt-get install -y python3 python3-pip

# 분석기 설치
git clone https://github.com/jimseokbea/-ci-failure-knowledge-graph.git /tmp/analyzer
pip3 install click pyyaml requests

# 설정 및 실행
cd /tmp/analyzer
cat > config.yml << EOF
github:
token: ${GITHUB_TOKEN}
repo: ${{ github.repository }}
database:
path: ./data/failures.db
ingestion:
days_back: 30
EOF

python3 cli.py ingest
FAILURE_ID=$(python3 cli.py latest-id || echo "")

if [ -n "$FAILURE_ID" ]; then
echo "## 🔴 CI 실패 분석 리포트" >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
echo "> **먼저 이 요약을 읽고, 아래 FixLink나 유사 실패를 참고하세요.**" >> $GITHUB_STEP_SUMMARY
echo "> 로그를 직접 열기 전에 해결책을 찾을 수 있습니다." >> $GITHUB_STEP_SUMMARY
echo "" >> $GITHUB_STEP_SUMMARY
python3 cli.py report --id $FAILURE_ID --format md >> $GITHUB_STEP_SUMMARY
fi

- name: Fail if tests failed
if: steps.test.outcome == 'failure'
run: exit 1
57 changes: 57 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,58 @@
# java-lotto-precourse

---

## CI 실패 시 확인 방법

> **CI가 실패했다면 아래 순서대로 확인하세요:**

1. **Actions → 실패한 Job → Step Summary를 먼저 확인**
2. **"[ACTION] 바로 해결하기" 섹션의 안내를 따르세요**
3. **유사 실패의 FixLink가 있다면 그것을 우선 참고하세요**
4. **로그를 직접 열기 전에 요약만으로 해결할 수 있습니다**
5. **반복되는 실패는 시그니처로 자동 매칭됩니다**

---

## 기능 목록

### 1. 입력 기능
- [ ] 구입 금액 입력받기
- [ ] 당첨 번호 입력받기 (쉼표 구분)
- [ ] 보너스 번호 입력받기

### 2. 입력 검증 기능
- [ ] 구입 금액이 1,000원 단위인지 검증
- [ ] 로또 번호가 1~45 범위인지 검증
- [ ] 로또 번호가 6개인지 검증
- [ ] 로또 번호에 중복이 없는지 검증
- [ ] 보너스 번호가 당첨 번호와 중복되지 않는지 검증
- [ ] 에러 발생 시 "[ERROR]" 메시지 출력 후 재입력

### 3. 로또 발행 기능
- [ ] 구입 금액으로 로또 개수 계산
- [ ] 1~45 중 중복되지 않는 6개 숫자 생성
- [ ] 로또 번호 오름차순 정렬

### 4. 당첨 확인 기능
- [ ] 로또 번호와 당첨 번호 비교
- [ ] 일치하는 번호 개수 세기
- [ ] 보너스 번호 일치 여부 확인
- [ ] 당첨 등수 판정 (Enum 사용)

### 5. 당첨 통계 기능
- [ ] 등수별 당첨 개수 집계
- [ ] 총 수익 계산
- [ ] 수익률 계산 및 반올림

### 6. 출력 기능
- [ ] 구매한 로또 개수 출력
- [ ] 구매한 로또 번호 출력 (오름차순)
- [ ] 당첨 통계 출력
- [ ] 수익률 출력

### 7. Enum 구현
- [ ] 당첨 등수 Enum (Rank)
- 일치 개수
- 보너스 일치 여부
- 당첨 금액
5 changes: 3 additions & 2 deletions src/main/java/lotto/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

public class Application {
public static void main(String[] args) {
// TODO: 프로그램 구현
LottoGame game = new LottoGame();
game.play();
}
}
}
101 changes: 101 additions & 0 deletions src/main/java/lotto/InputValidator.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
package lotto;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class InputValidator {
private static final int LOTTO_PRICE = 1000;
private static final int MIN_LOTTO_NUMBER = 1;
private static final int MAX_LOTTO_NUMBER = 45;
private static final int LOTTO_NUMBER_COUNT = 6;

public static int validatePurchaseAmount(String input) {
int amount = parseToInt(input, "Purchase amount must be a number.");
validatePositive(amount);
validateDivisible(amount);
return amount;
}

private static void validatePositive(int amount) {
if (amount <= 0) {
throw new IllegalArgumentException("[ERROR] Purchase amount must be positive.");
}
}

private static void validateDivisible(int amount) {
if (amount % LOTTO_PRICE != 0) {
throw new IllegalArgumentException("[ERROR] Purchase amount must be divisible by 1000.");
}
}

public static List<Integer> validateWinningNumbers(String input) {
String[] tokens = input.split(",");
validateNumberCount(tokens);

List<Integer> numbers = parseToIntegers(tokens);
validateRange(numbers);
validateDuplicate(numbers);

return numbers;
}

private static void validateNumberCount(String[] tokens) {
if (tokens.length != LOTTO_NUMBER_COUNT) {
throw new IllegalArgumentException("[ERROR] Winning numbers must be 6.");
}
}

private static List<Integer> parseToIntegers(String[] tokens) {
try {
return java.util.Arrays.stream(tokens)
.map(String::trim)
.map(Integer::parseInt)
.toList();
} catch (NumberFormatException e) {
throw new IllegalArgumentException("[ERROR] Winning numbers must be numbers.");
}
}

private static void validateRange(List<Integer> numbers) {
for (int number : numbers) {
if (number < MIN_LOTTO_NUMBER || number > MAX_LOTTO_NUMBER) {
throw new IllegalArgumentException("[ERROR] Winning numbers must be between 1 and 45.");
}
}
}

private static void validateDuplicate(List<Integer> numbers) {
Set<Integer> uniqueNumbers = new HashSet<>(numbers);
if (uniqueNumbers.size() != numbers.size()) {
throw new IllegalArgumentException("[ERROR] Winning numbers cannot be duplicated.");
}
}

public static int validateBonusNumber(String input, List<Integer> winningNumbers) {
int bonusNumber = parseToInt(input, "Bonus number must be a number.");
validateBonusRange(bonusNumber);
validateBonusNotInWinning(bonusNumber, winningNumbers);
return bonusNumber;
}

private static void validateBonusRange(int bonusNumber) {
if (bonusNumber < MIN_LOTTO_NUMBER || bonusNumber > MAX_LOTTO_NUMBER) {
throw new IllegalArgumentException("[ERROR] Bonus number must be between 1 and 45.");
}
}

private static void validateBonusNotInWinning(int bonusNumber, List<Integer> winningNumbers) {
if (winningNumbers.contains(bonusNumber)) {
throw new IllegalArgumentException("[ERROR] Bonus number cannot be same as winning numbers.");
}
}

private static int parseToInt(String input, String errorMessage) {
try {
return Integer.parseInt(input.trim());
} catch (NumberFormatException e) {
throw new IllegalArgumentException("[ERROR] " + errorMessage);
}
}
}
46 changes: 46 additions & 0 deletions src/main/java/lotto/InputView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package lotto;

import camp.nextstep.edu.missionutils.Console;

import java.util.List;

public class InputView {

public int inputPurchaseAmount() {
while (true) {
try {
System.out.println("구입금액을 입력해 주세요.");
String input = Console.readLine();
return InputValidator.validatePurchaseAmount(input);
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}

public List<Integer> inputWinningNumbers() {
while (true) {
try {
System.out.println();
System.out.println("당첨 번호를 입력해 주세요.");
String input = Console.readLine();
return InputValidator.validateWinningNumbers(input);
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}

public int inputBonusNumber(List<Integer> winningNumbers) {
while (true) {
try {
System.out.println();
System.out.println("보너스 번호를 입력해 주세요.");
String input = Console.readLine();
return InputValidator.validateBonusNumber(input, winningNumbers);
} catch (IllegalArgumentException e) {
System.out.println(e.getMessage());
}
}
}
}
51 changes: 47 additions & 4 deletions src/main/java/lotto/Lotto.java
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
package lotto;

import java.util.HashSet;
import java.util.List;
import java.util.Set;

public class Lotto {
private static final int LOTTO_NUMBER_COUNT = 6;
private static final int MIN_LOTTO_NUMBER = 1;
private static final int MAX_LOTTO_NUMBER = 45;

private final List<Integer> numbers;

public Lotto(List<Integer> numbers) {
Expand All @@ -11,10 +17,47 @@ public Lotto(List<Integer> numbers) {
}

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

private void validateSize(List<Integer> numbers) {
if (numbers.size() != LOTTO_NUMBER_COUNT) {
throw new IllegalArgumentException("[ERROR] Lotto numbers must be 6.");
}
}

private void validateRange(List<Integer> numbers) {
for (int number : numbers) {
validateNumberRange(number);
}
}

private void validateNumberRange(int number) {
if (number < MIN_LOTTO_NUMBER || number > MAX_LOTTO_NUMBER) {
throw new IllegalArgumentException("[ERROR] Lotto numbers must be between 1 and 45.");
}
}

// TODO: 추가 기능 구현
}
private void validateDuplicate(List<Integer> numbers) {
Set<Integer> uniqueNumbers = new HashSet<>(numbers);
if (uniqueNumbers.size() != LOTTO_NUMBER_COUNT) {
throw new IllegalArgumentException("[ERROR] Lotto numbers cannot be duplicated.");
}
}

public int countMatch(List<Integer> winningNumbers) {
return (int) numbers.stream()
.filter(winningNumbers::contains)
.count();
}

public boolean containsBonus(int bonusNumber) {
return numbers.contains(bonusNumber);
}

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

import java.util.List;

public class LottoGame {
private final InputView inputView;
private final OutputView outputView;
private final LottoMachine lottoMachine;

public LottoGame() {
this.inputView = new InputView();
this.outputView = new OutputView();
this.lottoMachine = new LottoMachine();
}

public void play() {
int purchaseAmount = inputView.inputPurchaseAmount();
List<Lotto> lottos = lottoMachine.purchase(purchaseAmount);
outputView.printPurchaseResult(lottos);

WinningNumbers winningNumbers = getWinningNumbers();

LottoResult result = checkLottos(lottos, winningNumbers);
outputView.printStatistics(result, purchaseAmount);
}

private WinningNumbers getWinningNumbers() {
List<Integer> numbers = inputView.inputWinningNumbers();
int bonusNumber = inputView.inputBonusNumber(numbers);
return new WinningNumbers(numbers, bonusNumber);
}

private LottoResult checkLottos(List<Lotto> lottos, WinningNumbers winningNumbers) {
LottoResult result = new LottoResult();
for (Lotto lotto : lottos) {
Rank rank = winningNumbers.match(lotto);
result.addResult(rank);
}
return result;
}
}
Loading