Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

K0seoyoung #401

Open
wants to merge 20 commits into
base: main
Choose a base branch
from
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
64 changes: 63 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,63 @@
# javascript-lotto-precourse
# 💸 로또 기능 구현


[🐣 입력받고 처리하기](#구입-금액-입력) 
[💰 발행하기](#발행하기) 
[🎉 당첨 계산하기](#당첨) 
[📈 수익률](#-수익률) 
[🤑 출력하기](#-출력하기)


## 🐣 입력 받고 처리하기
### 구입 금액 입력

1000원 단위로 나누어 떨어지지 않으면 예외 처리한다.

- `[ERROR] 구입 금액이 1000 단위로 나누어 떨어지지 않습니다.`

### 당첨 번호, 보너스 번호 입력

당첨 번호를 입력한다. 이때 쉼표로 구분한다.
- 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑는다.

- 예외 : **당첨 번호를 6개 입력하지 않은 경우** : <br>`[ERROR] 당첨 번호를 6개 입력해야합니다.`
- 예외 : **중복되는 경우** : <br>`[ERROR] 로또 번호는 중복 없는 6개의 숫자여야 합니다.`
- 예외 : **숫자 범위를 벗어난 경우** : <br>`[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.`

보너스 번호를 입력한다.
- 예외 : **보너스 번호가 1개가 아닌 경우** : <br>`[ERROR] 보너스 번호를 1개 입력해야합니다.`
- 예외 : **당첨 번호와 중복되는 경우**: <br>`[ERROR] 당첨 번호와 중복된 숫자를 입력했습니다.`
- 예외 : **숫자 범위를 벗어난 경우** : <br>`[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.`

## 💰 발행하기

나눈 숫자만큼 로또를 발행한다.
- 로또 번호의 숫자 범위 : 1-45

한 번 발행할 때 숫자의 개수는 6개 이다.


## 🎉 당첨 계산하기

1등 부터 5등까지 요구 사항에 따라 당첨자를 가린다.
- 6개 일치와 5개 일치에 보너스 볼 일치하는 경우를 잘 구분해야한다.
- 3개, 4개, 5개, 5개랑 보너스 맞춘경우, 6개로 나누고 `if-else`조건문으로 해결(특정 경우에만 조건을 수행하고 이 후 조건 무시하기 때문에 사용했습니다.)


## 📈 수익률

- $(당첨 총 금액/구입 금액) × 100 = 수익률$
- 소수점 **둘째자리**에서 반올림

<br />

## 🤑 출력하기

- 발행한 로또 수량과 번호를 출력한다.
- 오름차순으로 정렬하여 보여준다.

- 당첨 내역을 출력한다.
- 0개 일치, (00000원) - 0개
- 수익률 소수점 둘째 자리에서 반올림한다.

- 예외 사항 시 에러 문구 출력해야한다.
7 changes: 2 additions & 5 deletions __tests__/ApplicationTest.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
import App from "../src/App.js";
import App from "../src/controllers/App.js";
import { MissionUtils } from "@woowacourse/mission-utils";

const mockQuestions = (inputs) => {
MissionUtils.Console.readLineAsync = jest.fn();

MissionUtils.Console.readLineAsync.mockImplementation(() => {
const input = inputs.shift();

return Promise.resolve(input);
});
};
Expand Down Expand Up @@ -91,7 +90,5 @@ describe("로또 테스트", () => {
});
});

test("예외 테스트", async () => {
await runException("1000j");
});

});
45 changes: 45 additions & 0 deletions __tests__/InputValidatorTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import InputValidator from "../src/utils/InputValidator";

describe('입력 값 유효성 테스트', () => {
test("구입 금액이 1000원 단위가 아닌 경우 예외 처리", () => {
expect(() => {
InputValidator.PurchaseAmount("1500");
}).toThrow("[ERROR] 구입 금액이 1000 단위로 나누어 떨어지지 않습니다.");
});

test("당첨 번호가 범위를 벗어날 때 예외 처리", () => {
expect(() => {
InputValidator.WinningNumbers("1,2,3,4,5,46");
}).toThrow("[ERROR]");
});

test("당첨 번호가 6개가 아닐 때 예외 처리", () => {
expect(() => {
InputValidator.WinningNumbers("1,2,3,4,5");
}).toThrow("[ERROR]");
});

test("당첨 번호에 중복된 숫자가 있을 때 예외 처리", () => {
expect(() => {
InputValidator.WinningNumbers("1,2,3,4,5,5");
}).toThrow("[ERROR]");
});

test("보너스 번호가 1개가 아닐 때 예외 처리", () => {
expect(() => {
InputValidator.bonusNumber("7,8", [1, 2, 3, 4, 5, 6]);
}).toThrow("[ERROR] 보너스 번호를 1개 입력해야 합니다.");
});

test("보너스 번호가 당첨 번호와 중복될 때 예외 처리", () => {
expect(() => {
InputValidator.bonusNumber("5", [1, 2, 3, 4, 5, 6]);
}).toThrow("[ERROR] 당첨 번호와 중복된 숫자를 입력했습니다.");
});

test("보너스 번호가 범위를 벗어날 때 예외 처리", () => {
expect(() => {
InputValidator.bonusNumber("50", [1, 2, 3, 4, 5, 6]);
}).toThrow("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.");
});
});
11 changes: 9 additions & 2 deletions __tests__/LottoTest.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import Lotto from "../src/Lotto";
import Lotto from "../src/models/Lotto";

describe("로또 클래스 테스트", () => {
test("로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.", () => {
Expand All @@ -14,5 +14,12 @@ describe("로또 클래스 테스트", () => {
}).toThrow("[ERROR]");
});

// TODO: 추가 기능 구현에 따른 테스트 코드 작성
test("로또 번호가 1~45 범위를 벗어나면 예외가 발생한다.", () => {
expect(() => {
new Lotto([0, 2, 3, 4, 5, 6]);
}).toThrow("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.");
expect(() => {
new Lotto([1, 2, 3, 4, 5, 46]);
}).toThrow("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.");
});
});
5 changes: 0 additions & 5 deletions src/App.js

This file was deleted.

18 changes: 0 additions & 18 deletions src/Lotto.js

This file was deleted.

48 changes: 48 additions & 0 deletions src/controllers/App.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Console } from "@woowacourse/mission-utils";
import LottoManager from "../models/LottoManager.js";
import InputValidator from "../utils/InputValidator.js";
import ConsoleView from "../views/ConsoleView.js";

class App {
async run() {
ConsoleView.askPurchaseAmount((input) => this.handlePurchaseAmount(input));
}

handlePurchaseAmount(input) {
try {
const purchaseAmount = InputValidator.PurchaseAmount(input);
console.log("구입 금액 유효성 통과:", purchaseAmount); // 디버깅 로그
this.lottoManager = new LottoManager(purchaseAmount);
ConsoleView.showLottos(this.lottoManager.getLottos());
ConsoleView.askWinningNumbers((input) => this.handleWinningNumbers(input));
} catch (error) {
ConsoleView.showError(error);
ConsoleView.askPurchaseAmount((input) => this.handlePurchaseAmount(input));
}
}

handleWinningNumbers(input) {
try {
const winningNumbers = InputValidator.WinningNumbers(input);
console.log("당첨 번호 유효성 통과:", winningNumbers); // 디버깅 로그
ConsoleView.askBonusNumber((input) => this.handleBonusNumber(input, winningNumbers));
} catch (error) {
ConsoleView.showError(error);
ConsoleView.askWinningNumbers((input) => this.handleWinningNumbers(input));
}
}

handleBonusNumber(input, winningNumbers) {
try {
const bonusNumber = InputValidator.BonusNumber(input);
console.log("보너스 번호 유효성 통과:", bonusNumber); // 디버깅 로그
const results = this.lottoManager.calculateResults(winningNumbers, bonusNumber);
ConsoleView.showResults(results);
} catch (error) {
ConsoleView.showError(error);
ConsoleView.askBonusNumber((input) => this.handleBonusNumber(input, winningNumbers));
}
}
}

export default App;
2 changes: 1 addition & 1 deletion src/index.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import App from "./App.js";
import App from "./controllers/App.js";

const app = new App();
await app.run();
28 changes: 28 additions & 0 deletions src/models/Lotto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
class Lotto {
#numbers;

constructor(numbers) {
this.#validate(numbers);
this.#numbers = numbers;
}

#validate(numbers) {
if (numbers.length !== 6) {
throw new Error("[ERROR] 로또 번호는 6개여야 합니다.");
}
if (new Set(numbers).size !== numbers.length){
throw new Error("[ERROR] 로또 번호는 중복 없는 6개의 숫자여야 합니다.")
}
if (numbers.some(num => num < 1 || num > 45)){
throw new Error("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.")
}
}

// TODO: 추가 기능 구현

getNumbers(){
return this.#numbers;
}
}

export default Lotto;
27 changes: 27 additions & 0 deletions src/models/LottoManager.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import Lotto from "./Lotto.js";
import { Random } from "@woowacourse/mission-utils";
import ResultCalculator from "./ResultCalculator.js";

class LottoManager {
constructor(purchaseAmount) {
this.lottos = this.generateLottos(purchaseAmount / 1000);
}

generateLottos(count) {
return Array.from({ length: count }, () => {
const numbers = Random.pickUniqueNumbersInRange(1, 45, 6).sort((a, b) => a - b);
return new Lotto(numbers);
});
}

getLottos() {
return this.lottos;
}

calculateResults(winningNumbers, bonusNumber) {
const resultCalculator = new ResultCalculator(this.lottos, winningNumbers, bonusNumber);
return resultCalculator.getResults();
}
}

export default LottoManager;
35 changes: 35 additions & 0 deletions src/models/ResultCalculator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
class ResultCalculator {
constructor(lottos, winningNumbers, bonusNumber) {
this.lottos = lottos;
this.winningNumbers = winningNumbers;
this.bonusNumber = bonusNumber;
this.matchCounts = { 3: 0, 4: 0, 5: 0, 6: 0, "5+bonus": 0 };
this.calculateResults();
}

calculateResults() {
this.lottos.forEach((lotto) => {
const matchedCount = this.countMatches(lotto.getNumbers());
this.updateMatchCounts(matchedCount, lotto);
});
}

countMatches(numbers) {
return numbers.filter((num) => this.winningNumbers.includes(num)).length;
}

updateMatchCounts(matchedCount, lotto) {
if (matchedCount === 6) this.matchCounts[6]++;
else if (matchedCount === 5 && lotto.getNumbers().includes(this.bonusNumber)) this.matchCounts["5+bonus"]++;
else if (matchedCount === 5) this.matchCounts[5]++;
else if (matchedCount === 4) this.matchCounts[4]++;
else if (matchedCount === 3) this.matchCounts[3]++;
}

getResults() {
return this.matchCounts;
}
}

export default ResultCalculator;

42 changes: 42 additions & 0 deletions src/utils/InputValidator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import Lotto from "../models/Lotto.js";

class InputValidator {
static PurchaseAmount(input) {
const amount = parseInt(input, 10);
if (isNaN(amount) || amount % 1000 !== 0) {
throw new Error("[ERROR] 구입 금액이 1000 단위로 나누어 떨어지지 않습니다.");
}
return amount;
}

static WinningNumbers(input) {
const numbers = input.split(",").map(Number);
const lotto = new Lotto(numbers);
return lotto.getNumbers();
}

static bonusNumber(input, winningNumbers) {
const numbers = input.split(',').map(Number);
this.checkBonusNumberCount(numbers);
const bonusNumber = numbers[0];
this.checkBonusNumberValidity(bonusNumber, winningNumbers);
return bonusNumber;
}

static checkBonusNumberCount(numbers) {
if (numbers.length !== 1) {
throw new Error("[ERROR] 보너스 번호를 1개 입력해야 합니다.");
}
}

static checkBonusNumberValidity(bonusNumber, winningNumbers) {
if (winningNumbers.includes(bonusNumber)) {
throw new Error("[ERROR] 당첨 번호와 중복된 숫자를 입력했습니다.");
}
if (isNaN(bonusNumber) || bonusNumber < 1 || bonusNumber > 45) {
throw new Error("[ERROR] 로또 번호는 1부터 45 사이의 숫자여야 합니다.");
}
}
}

export default InputValidator;
Loading