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
34 changes: 34 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,35 @@
# javascript-lotto-precourse

### 랜덤

- [x] 숫자의 범위 1~45 중 랜덤

### 입력

- [x] 로또 구입 금액

- [x] 1000원 단위로 안떨어지면 예외

- [x] 당첨 번호 입력 쉼표(,)를 기준으로

- [x] 보너스 번호 입력

### 출력

- [x] 발행한 로또 수 만큼 번호 출력

- [x] 로또 번호 오름 차순

- [x] 당첨 내역 출력

- [x] 수익률( 소수점 둘째 자리 반올림)

### 예외

- [x]1000원 단위로 안떨어질때

- [x] 로또 입력 번호가 1~45가 아닐시

- [x] 보너스 번호 하나 이상일시

- [x] 로또 번호에 문자 입력 시
7 changes: 6 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,10 @@
import LottoController from "../src/controller/Controller.js";

class App {
async run() {}
async run() {
const lottoController = new LottoController();
await lottoController.start(); // 전체 애플리케이션 시작
}
}

export default App;
12 changes: 11 additions & 1 deletion src/Lotto.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,19 @@ class Lotto {
if (numbers.length !== 6) {
throw new Error("[ERROR] 로또 번호는 6개여야 합니다.");
}
const isInRange = numbers.every((num) => num >= 1 && num <= 45);
if (!isInRange) {
throw new Error("[ERROR] 로또 번호는 1부터 45 사이여야 합니다.");
}
const hasDuplicates = new Set(numbers).size !== numbers.length;
if (hasDuplicates) {
throw new Error("[ERROR] 로또 번호는 중복되지 않아야 합니다.");
}
}

// TODO: 추가 기능 구현
getNumbers() {
return this.#numbers;
}
}

export default Lotto;
125 changes: 125 additions & 0 deletions src/controller/Controller.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
import InputView from "../view/InputView.js";
import OutputView from "../view/OutputView.js";
import Lotto from "../Lotto.js";
import WinningLotto from "../model/WinningLotto.js";
import LottoRandom from "../model/LottoRandom.js";
import WinningResult from "../model/WinningResult.js";

class LottoController {
constructor() {
this.inputView = new InputView();
this.outputView = new OutputView();
this.lottoRandom = new LottoRandom();
this.winningResult = new WinningResult();
this.lottos = [];
this.purchaseAmount = 0;
this.bonusNumber = 0;
}

async start() {
try {
await this.setPurchaseAmount();
await this.generateLottos();
const winningNumbers = await this.getWinningNumbers();
this.bonusNumber = await this.getBonusNumber();
const result = await this.calculateResults(winningNumbers);
this.displayResults(result);
} catch (error) {
this.outputView.printError(error.message);
this.start();
}
}

async setPurchaseAmount() {
const purchaseAmount = await this.inputView.getPurchaseAmount();
this.purchaseAmount = this.validatePurchaseAmount(Number(purchaseAmount));
}

validatePurchaseAmount(amount) {
if (isNaN(amount) || amount % 1000 !== 0) {
throw new Error("[ERROR] 구입 금액은 1,000원 단위의 숫자여야 합니다.");
}
return amount;
}

async generateLottos() {
const purchaseCount = this.purchaseAmount / 1000;
const lottoNumbers = await this.lottoRandom.lottoRandomNumber(
purchaseCount
);
this.lottos = lottoNumbers.map((numbers) =>
new Lotto(numbers).getNumbers()
);
this.outputView.outputLottoNumbers(purchaseCount, this.lottos);
}

async getWinningNumbers() {
const winningInput = await this.inputView.getWinningNumbers();
const winningNumbers = await new WinningLotto().winningnumbers(
winningInput
);
return new Lotto(winningNumbers).getNumbers();
}

async getBonusNumber() {
const bonusInput = await this.inputView.getBonusNumber();
return this.validateBonusNumber(Number(bonusInput));
}

validateBonusNumber(number) {
if (isNaN(number) || number < 1 || number > 45) {
throw new Error("[ERROR] 보너스 번호는 1부터 45 사이의 숫자여야 합니다.");
}
return number;
}

async calculateResults(winningNumbers) {
const countMap = { 3: 0, 4: 0, 5: 0, "5+bonus": 0, 6: 0 };

this.lottos.forEach((lotto) => {
const matchCount = lotto.filter((num) =>
winningNumbers.includes(num)
).length;
if (matchCount === 6) {
countMap[6] += 1; // 1등
} else if (matchCount === 5 && lotto.includes(this.bonusNumber)) {
countMap["5+bonus"] += 1; // 2등
} else if (countMap[matchCount] !== undefined) {
countMap[matchCount] += 1;
}
});

const totalPrize = this.calculateTotalPrize(countMap);
const profitRate = this.calculateRateOfReturn(
totalPrize,
this.purchaseAmount
);
return { countMap, profitRate };
}

calculateRateOfReturn(totalPrize, purchaseAmount) {
const rate = (totalPrize / purchaseAmount) * 100;
return parseFloat(rate.toFixed(2));
}

calculateTotalPrize(countMap) {
const prizeMap = {
3: 5000,
4: 50000,
5: 1500000,
"5+bonus": 30000000,
6: 2000000000,
};
return Object.entries(countMap).reduce(
(total, [key, frequency]) => total + (prizeMap[key] || 0) * frequency,
0
);
}

displayResults({ countMap, profitRate }) {
this.outputView.outputWinningResult(countMap);
this.outputView.outputProfitRate(profitRate);
}
}

export default LottoController;
8 changes: 8 additions & 0 deletions src/model/BuyLotto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
class BuyLotto {
async buylottonumbers(totalPrize) {
const purchaseAmount = totalPrize / 1000;
return purchaseAmount;
}
}

export default BuyLotto;
13 changes: 13 additions & 0 deletions src/model/LottoRandom.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { MissionUtils } from "@woowacourse/mission-utils";

class LottoRandom {
async lottoRandomNumber(purchaseAmount) {
const lottoNumbers = Array.from({ length: purchaseAmount }, () => {
const numbers = MissionUtils.Random.pickUniqueNumbersInRange(1, 45, 6);
return numbers.sort((a, b) => a - b);
});
return lottoNumbers;
}
}

export default LottoRandom;
10 changes: 10 additions & 0 deletions src/model/WinningLotto.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
class WinningLotto {
async winningnumbers(winningNumbersString) {
const winningNumbers = winningNumbersString
.split(",")
.map((num) => Number(num.trim()));
return winningNumbers;
}
}

export default WinningLotto;
20 changes: 20 additions & 0 deletions src/model/WinningResult.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
class WinningResult {
async result(winningNumbers, lottos, bonusNumber) {
const countMap = { 3: 0, 4: 0, 5: 0, "5+bonus": 0, 6: 0 };

lottos.forEach((lotto) => {
const matchCount = lotto.filter((num) =>
winningNumbers.includes(num)
).length;
if (matchCount === 5 && lotto.includes(bonusNumber)) {
countMap["5+bonus"] += 1;
} else if (countMap[matchCount] !== undefined) {
countMap[matchCount] += 1;
}
});

return countMap;
}
}

export default WinningResult;
17 changes: 17 additions & 0 deletions src/view/InputView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { MissionUtils } from "@woowacourse/mission-utils";

class InputView {
async getPurchaseAmount() {
return MissionUtils.Console.readLineAsync("구입금액을 입력해 주세요.\n");
}

async getWinningNumbers() {
return MissionUtils.Console.readLineAsync("당첨 번호를 입력해주세요.\n");
}

async getBonusNumber() {
return MissionUtils.Console.readLineAsync("보너스 번호를 입력해주세요.\n");
}
}

export default InputView;
35 changes: 35 additions & 0 deletions src/view/OutputView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { MissionUtils } from "@woowacourse/mission-utils";

class OutputView {
async outputLottoNumbers(purchase, lottoNumbers) {
MissionUtils.Console.print(
`${purchase}개를 구매했습니다.\n` +
lottoNumbers.map((numbers) => `[${numbers.join(", ")}]`).join("\n")
);
}

async outputWinningResult(countMap) {
MissionUtils.Console.print("당첨 통계\n---");
MissionUtils.Console.print(`3개 일치 (5,000원) - ${countMap[3] || 0}개`);
MissionUtils.Console.print(`4개 일치 (50,000원) - ${countMap[4] || 0}개`);
MissionUtils.Console.print(
`5개 일치 (1,500,000원) - ${countMap[5] || 0}개`
);
MissionUtils.Console.print(
`5개 일치, 보너스 볼 일치 (30,000,000원) - ${countMap["5+bonus"] || 0}개`
);
MissionUtils.Console.print(
`6개 일치 (2,000,000,000원) - ${countMap[6] || 0}개`
);
}

async outputProfitRate(profitRate) {
MissionUtils.Console.print(`총 수익률은 ${profitRate}%입니다.`);
}

printError(errorMessage) {
MissionUtils.Console.print(errorMessage);
}
}

export default OutputView;