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

## 기능 목록

1. 사용자로부터 로또 구매 금액을 입력 받는다.
- 입력된 금액의 유효성을 검사한다.

2. 발급된 로또 수량 및 오름차순으로 정렬된 로또 번호를 출력한다.

3. 쉼표(,)를 기준으로 당첨 번호를 입력 받는다.
- 입력된 당첨 번호의 유효성을 검사한다.

4. 사용자로부터 보너스 번호를 입력 받는다.
- 보너스 번호의 유효성을 검사한다.

5. 당첨 통계 정보를 출력한다.

6. 총 수익률을 소수점 둘째 자리에서 반올림하여 표시한다.

---

## 예외 처리 사항

### 보너스 번호 검증
- 입력 값은 1부터 45 사이의 숫자여야 합니다.
- 당첨 번호와 중복되어서는 안 됩니다.
- 단일 숫자만 입력할 수 있습니다.

### 당첨 번호 검증
- 모든 입력 번호는 1부터 45 사이의 숫자여야 합니다.
- 번호는 중복되지 않도록 해야 합니다.
- 각 번호는 쉼표로 구분하여 입력해야 합니다.
- 총 6개의 번호를 입력해야 합니다.

### 로또 구매 금액 검증
- 입력 값은 반드시 양수여야 합니다.
- 금액은 1000원 단위로 입력해야 합니다.
- 하나의 숫자만 입력해야 합니다.
6 changes: 5 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import LottoActions from "./LottoActions.js";

class App {
async run() {}
async run() {
await new LottoActions().play();
}
}

export default App;
40 changes: 35 additions & 5 deletions src/Lotto.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,48 @@
import { BONUS_MATCH_RANK_INDEX, LOTTO_MATCH_COUNT } from "./constants/lottoNumbers.js";
import InputValidate from "./utils/InputValidate.js";

const RANK = [
"firstPlace",
"thirdPlace",
"fourthPlace",
"fifthPlace",
"blank",
"blank",
"blank",
"secondPlace",
];



class Lotto {
#numbers;

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

#validate(numbers) {
if (numbers.length !== 6) {
throw new Error("[ERROR] 로또 번호는 6개여야 합니다.");
}
this.error.inputExist(numbers);
this.error.lottoNumberLength(numbers);
this.error.lottoNumberRange(numbers);
this.error.lottoNumberType(numbers);
this.error.duplicateLottoNumber(numbers);
}

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

compareLotto(winningNumber, bonusNumber) {

const count = this.#numbers.filter((value) => winningNumber.includes(value)).length;
if(this.#numbers.includes(bonusNumber) && count === LOTTO_MATCH_COUNT){
return RANK[BONUS_MATCH_RANK_INDEX];
}
return RANK[this.#numbers.length - count];
}
}

export default Lotto;
export default Lotto;
149 changes: 149 additions & 0 deletions src/LottoActions.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
import { Console } from "@woowacourse/mission-utils";
import InputValidate from "./utils/InputValidate.js";
import LottoModel from "./model/lottoModel.js";

class LottoActions {
constructor() {
this.error = new InputValidate();
this.model = new LottoModel();
}

async play() {
await this.issueLotto();
this.getInformation();
}

async issueLotto() {
const userPrice = await this.inputPrice();
this.getLottoList(userPrice);

const winningNumber = await this.inputWinningNumber();
this.model.setWinningNumber(winningNumber);

const bonusNumber = await this.inputBonusNumber();
this.model.setBonusNumber(bonusNumber);
}

getInformation() {
this.model.setWinningStatistics();
this.printResultMessage();
this.printUserRank(this.model.getStatistics());
const profit = this.model.calculateProfit();
this.printProfit(profit);
}

async inputPrice() {
let isValid = false;
let price;
do {
price = await Console.readLineAsync("구입금액을 입력해 주세요.");
isValid = this.validateUserPrice(price);
} while (!isValid);
return price;
}

validateUserPrice(price) {
const validationMessage = this.error.priceInputValidate(price);
if (validationMessage) {
Console.print(validationMessage);
return false;
}
return true;
}

getLottoList(userPrice) {
this.model.setPrice(userPrice);
this.model.generateLottoNumber();
const lottoList = this.model.getLottoList();
this.printLottoList(lottoList);
}

async inputWinningNumber() {
let isValid = false;
let winningNumber;
do {
winningNumber = await Console.readLineAsync(
"\n당첨 번호를 입력해 주세요.\n"
);
isValid = this.validateWinningNumber(winningNumber.split(","));
} while (!isValid);
return winningNumber.split(",").map(Number);
}

validateWinningNumber(numbers) {
const validationMessage = this.error.lottoNumberValidate(numbers);
if (validationMessage) {
Console.print(validationMessage);
return false;
}
return true;
}

async inputBonusNumber() {
let bonusNumber;
let isValid = false;
do {
bonusNumber = await Console.readLineAsync(
"\n보너스 번호를 입력해 주세요.\n"
);
isValid = this.validateBonusNumber(bonusNumber);
} while (!isValid);
return Number(bonusNumber);
}

validateBonusNumber(bonusNumber) {
const validationMessage = this.error.bonusNumberValidate(
bonusNumber,
this.model.getWinningNumber()
);
if (validationMessage) {
Console.print(validationMessage);
return false;
}
return true;
}

printLottoList(lottoList) {
Console.print(`\n${lottoList.length}개를 구매했습니다.`);
for (let lotto of lottoList) {
Console.print(`[${lotto.getLottoNumber().join(", ")}]`);
}
}

printResultMessage() {
Console.print("\n당첨 통계");
Console.print("---");
}

printUserRank(userDetails) {
const [fifth, fourth, third, second, first] = [
userDetails.fifthPlace || 0,
userDetails.fourthPlace || 0,
userDetails.thirdPlace || 0,
userDetails.secondPlace || 0,
userDetails.firstPlace || 0,
];

const lottoArray = [
`3개 일치 (5,000원) - ${fifth}개`,
`4개 일치 (50,000원) - ${fourth}개`,
`5개 일치 (1,500,000원) - ${third}개`,
`5개 일치, 보너스 볼 일치 (30,000,000원) - ${second}개`,
`6개 일치 (2,000,000,000원) - ${first}개`,
];

this.printUserLotto(lottoArray);
}

printUserLotto(lottoArray) {
for (let message of lottoArray) {
Console.print(message);
}
}

printProfit(profit) {
Console.print(`총 수익률은 ${profit}%입니다.`);
}
}

export default LottoActions;
13 changes: 13 additions & 0 deletions src/constants/error.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
export const ERROR_MESSAGE = {
EMPTY_INPUT: "[ERROR] 입력값이 비어 있습니다. 값을 입력해 주세요.",
INVALID_PRICE_TYPE: "[ERROR] 유효한 숫자를 입력해 주세요.",
INVALID_PRICE_UNIT: "[ERROR] 금액은 1,000원 단위로 입력해 주세요.",
NEGATIVE_PRICE: "[ERROR] 금액은 양수로 입력해 주세요.",
INVALID_LOTTO_RANGE: "[ERROR] 당첨 번호는 1에서 45 사이의 숫자여야 합니다.",
LOTTO_NUMBER_DUPLICATE: "[ERROR] 당첨 번호는 중복되지 않도록 입력해 주세요.",
INVALID_LOTTO_LENGTH: "[ERROR] 로또 번호는 6개의 숫자여야 합니다.",
INVALID_LOTTO_TYPE: "[ERROR] 숫자를 쉼표(,)로 구분하여 입력해 주세요.",
INVALID_BONUS_NUMBER_TYPE: "[ERROR] 보너스 번호는 하나의 숫자로 입력해 주세요.",
INVALID_BONUS_NUMBER_RANGE: "[ERROR] 보너스 번호는 1에서 45 사이의 숫자여야 합니다.",
BONUS_NUMBER_DUPLICATE: "[ERROR] 보너스 번호는 당첨 번호와 중복되지 않도록 입력해 주세요.",
};
11 changes: 11 additions & 0 deletions src/constants/lottoNumbers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
export const LOTTO_NUMBER_RANGE = {
MINIMUM : 1,
MAXIMUM : 45,
COUNT : 6,
}

export const PRICE_UNIT = 1000;

export const LOTTO_MATCH_COUNT = 5;

export const BONUS_MATCH_RANK_INDEX = 7;
78 changes: 78 additions & 0 deletions src/model/lottoModel.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
import { MissionUtils } from "@woowacourse/mission-utils";
import Lotto from "../Lotto.js";
import { LOTTO_NUMBER_RANGE, PRICE_UNIT } from "../constants/lottoNumbers.js";

const PRICE = {
firstPlace : 2000000000,
secondPlace : 30000000,
thirdPlace : 1500000,
fourthPlace : 50000,
fifthPlace : 5000
}

class LottoModel{
#userPrice
#winningNumber
#bonusNumber

constructor() {
this.#userPrice = 0;
this.lottoList = [];
this.#winningNumber = [];
this.#bonusNumber;
this.userDetails = {
fifthPlace : 0,
fourthPlace : 0,
thirdPlace : 0,
secondPlace : 0,
firstPlace : 0,
}
}
setPrice(price) {
this.#userPrice = price;
}

generateLottoNumber() {
const numberOfLotto = this.#userPrice/PRICE_UNIT;
for (let i = 0; i < numberOfLotto; i++) {
let number = MissionUtils.Random.pickUniqueNumbersInRange(LOTTO_NUMBER_RANGE.MINIMUM, LOTTO_NUMBER_RANGE.MAXIMUM, LOTTO_NUMBER_RANGE.COUNT).sort((a, b) => a - b);
let lotto = new Lotto(number);
this.lottoList.push(lotto);
}
}
getLottoList() {
return this.lottoList;
}

setWinningNumber(numbers) {
this.#winningNumber = numbers;
}
getWinningNumber() {
return this.#winningNumber;
}
setBonusNumber(bonusNumber) {
this.#bonusNumber = bonusNumber;
}
setWinningStatistics() {
this.lottoList.forEach((lotto) => {
let lottoRank = lotto.compareLotto(this.#winningNumber, this.#bonusNumber);
if(lottoRank in this.userDetails){
this.userDetails[lottoRank] += 1;
}
})
}
getStatistics() {
return this.userDetails;
}

calculateProfit() {
let total = 0;
for (const [key, count] of Object.entries(this.userDetails)) {
total += PRICE[key]*count;
};
const profit = (total/this.#userPrice)*100;
return Math.round(profit*100)/100;
}
}

export default LottoModel;
Loading