Skip to content
52 changes: 52 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,53 @@
# javascript-lotto-precourse
✅ 입출력
- 로또 구입 금액 입력(1000단위) ✅
- 당첨 번호 입력 ✅
- 보너스 번호 입력 ✅
- 발행한 로또 개수 출력 ✅
- 결과 출력

✅ 전처리
- 당첨 번호 쉼표 기준 구분(앞 뒤로 공백제거, 숫자로 변환) ✅
- 발행한 로또 개수 구하기 ✅

✅ 1등 ~ 5등 검사
- (로또 개수만큼) 중복되지 않는 숫자 6개 뽑기, 오름차순 정렬 ✅
- 1등 조건 검사, 개수 카운트 ✅
- 2등 조건 검사, 개수 카운트 ✅
- 3등 조건 검사, 개수 카운트 ✅
- 4등 조건 검사, 개수 카운트 ✅
- 5등 조건 검사, 개수 카운트 ✅

✅ 수익률 계산
- 수익률 계산 (벌어들인 금액/구매 금액), 소수점 둘째 자리 반올림 ✅

✅ 예외처리
- 입력 오류 : 입력 금액이 1000으로 나누어 떨어지지 않는 경우 ✅
- 입력 오류 : 로또 구매 금액이 숫자가 경우 ✅
- 입력 오류 : 숫자가 아니거나, 쉼표가 아닌 것이 입력된 경우 ✅
- 입력 오류 : 숫자가 중복되는 경우 ✅
- 입력 오류 : 로또 번호가 범위를 벗어나는 경우 ✅
- 입력 오류 : 로또 번호가 6개가 아닌 경우 ✅
- 입력 오류 : 보너스 번호가 이미 입력한 번호와 중복되는 경우 ✅
- 입력 오류 : 보너스 번호가 숫자가 아닌 경우 ✅

# 클래스 설계
- Lotto 클래스 : 입력된 로또 번호 검증 및 관리
- LottoGame 클래스 : 게임진행, 로또발행, 당첨내역, 예외처리
- App 클래스 : 입출력 담당

# 코드 흐름
- App 로또 금액 입력받기
-> LottoGame 로또 금액만큼 로또 발행 요청
-> Lotto 로또 발행 (외부에서 수정 불가)
-> LottoGame 발행된 로또 저장
-> App 로또출력

- App 로또 번호, 보너스 입력받기
-> LottoGame 로또 번호 전처리(,제거)
-> Lotto 유저 로또 검증, 발행
-> LottoGame 유저 로또 저장
-> LottoGame 로또 번호 당첨 여부 카운트
-> App 당첨여부출력

# MVC 구조
83 changes: 82 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,86 @@
import { Console } from "@woowacourse/mission-utils";
import LottoGame from "../src/LottoGame.js";

class App {
async run() {}
async run() {
try {
const lottoGame = new LottoGame();

//구입금액 입력
const inputMoney = await this.getInputMoney();
const ticketCount = inputMoney / 1000;

//구입한 로또 출력
Console.print(`${ticketCount}개를 구매했습니다.`);
const winningLotto = lottoGame.creatLotto(ticketCount);
for (let lotto in winningLotto) {
Console.print(`[${winningLotto[lotto].join(", ")}]`);
}

//당첨번호 입력
const userLotto = await this.getUserLotto(lottoGame);

//보너스번호 입력
const inputBonus = await this.getBonusNumber(userLotto);

//결과 출력
const rank = lottoGame.returnRank(userLotto, winningLotto, inputBonus);
Console.print("당첨 통계");
Console.print("---");
Console.print(`3개 일치 (5,000원) - ${rank.fifth}개`);
Console.print(`4개 일치 (50,000원) - ${rank.fourth}개`);
Console.print(`5개 일치 (1,500,000원) - ${rank.third}개`);
Console.print(`5개 일치, 보너스 볼 일치 (30,000,000원) - ${rank.second}개`);
Console.print(`6개 일치 (2,000,000,000원) - ${rank.first}개`);
const earningRate = lottoGame.earningRate(inputMoney);
Console.print(`총 수익률은 ${earningRate}%입니다.`);

} catch (error) {
Console.print(error.message);
return Promise.reject(error);
}
}

// 재귀적으로 구입금액 입력 받기
async getInputMoney() {
try {
const inputMoney = await Console.readLineAsync('구입금액을 입력해 주세요.');
const moneyNum = Number(inputMoney);
if (isNaN(moneyNum) || inputMoney % 1000 !== 0 || !Number.isInteger(moneyNum)) {
throw new Error("[ERROR] IllegalArgumentException");
}
return inputMoney;
} catch (error) {
Console.print(error.message);
return this.getInputMoney(); // 재귀적으로 다시 호출
}
}

// 재귀적으로 당첨번호 입력 받기
async getUserLotto(lottoGame) {
try {
const inputNumbers = await Console.readLineAsync('당첨 번호를 입력해 주세요.');
const userLotto = lottoGame.preprocessUserLotto(inputNumbers);
return userLotto;
} catch (error) {
Console.print(error.message);
return this.getUserLotto(lottoGame); // 재귀적으로 다시 호출
}
}

// 재귀적으로 보너스 번호 입력 받기
async getBonusNumber(userLotto) {
try {
const inputBonus = await Console.readLineAsync('보너스 번호를 입력해 주세요.');
if (userLotto.includes(Number(inputBonus))||isNaN(inputBonus)) {
throw new Error("[ERROR] 보너스 번호는 입력하지 않은 숫자이어야 합니다.");
}
return inputBonus;
} catch (error) {
Console.print(error.message);
return this.getBonusNumber(userLotto); // 재귀적으로 다시 호출
}
}
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

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

App 클래스에서는 사용자와 맞닿아있는 인터페이스 (UI) 부분을 구현하는 게 가독성이 좋아보일 것 같아요. 사용자가 디폴트로 거쳐야 하는 기능의 UI만 골라서 구현하시면 코드가 훨씬 간결해질 것 같습니다! 저의 경우는 예외처리 및 결과 출력 부분 등 기능별로 파일을 여러개 만들어서 별도로 구현하였습니다


export default App;
9 changes: 7 additions & 2 deletions src/Lotto.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
import { Random } from "@woowacourse/mission-utils";

//로또 번호 저장, 검증, 반환
class Lotto {
#numbers;
#numbers; //받은 로또 번호리스트

constructor(numbers) {
this.#validate(numbers);
Expand All @@ -12,7 +15,9 @@ class Lotto {
}
}

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

Choose a reason for hiding this comment

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

Lotto 클래스에서는 로또 티켓이 가진 고유의 성질들을 구현하시면 좋을 것 같아요. 저는 로또 번호 6를 랜덤으로 뽑는 것이라던지 당첨번호를 입력받을 때의 예외 처리를 이 부분에서 구현하였습니다.


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

class LottoGame{
constructor(){
this.winningLottos = []
this.counts = {first : 0, second : 0, third: 0, fourth: 0, fifth: 0};
}

//로또 발행
creatLotto(num){
for(let i = 0; i < num; i++){
let numbers = Random.pickUniqueNumbersInRange(1, 45, 6); //중복되지 않는 숫자 6개
this.winningLottos.push(new Lotto(numbers).getNumbers());
}
this.winningLottos.sort((a,b) => a - b);
return this.winningLottos
}

// userLotto 전처리
preprocessUserLotto(userNumbers) {
userNumbers = userNumbers.split(",").map(num => num.trim()).map(num => Number(num));
const userLotto = new Lotto(userNumbers);
return userLotto.getNumbers().sort((a,b) => a - b);
}

// 일치하는 숫자 개수 계산
calculateMatchingCount(userLotto, winningLotto) {
return userLotto.filter(num => winningLotto.includes(num)).length;
}

// 등수 판별
getRank(matchingCount, bonusMatch) {
switch (matchingCount) {
case 6: return 'first';
case 5: return bonusMatch ? 'third' : 'second';
case 4: return 'fourth';
case 3: return 'fifth';
default: return null;
}
}

// 등수 반환
returnRank(userLotto, winningLottos, bonusNumber){
let matchingCount = 0;
let bonusMatch = userLotto.includes(bonusNumber);

for (let i = 0; i < winningLottos.length; i++) {
// 일치하는 번호 개수 계산
matchingCount = this.calculateMatchingCount(userLotto, winningLottos[i]);

// 등수 판별
let rank = this.getRank(matchingCount, bonusMatch);
if (rank) {
this.counts[rank] += 1;
}
}

return this.counts;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

결과 출력하는 부분의 구현을 이부분에 해놓고 App에서는 함수 호출을 하면 위에 수익률 계산 하는 함수에 대한 결과를 바로 밑에서 볼 수 있어서 가독성이 좋을 것 같습니다!

//수익률 계산 (벌어들인 금액/구매 금액)
earningRate(money){
const earnings = (this.counts.fifth * 5000) +
(this.counts.fourth * 50000) +
(this.counts.second * 1500000) +
(this.counts.third * 30000000) +
(this.counts.first * 2000000000);

const earningsRate = (earnings / money) * 100 ;
const roundedearningsRate = parseFloat(earningsRate.toFixed(1));
return roundedearningsRate;
}
}

export default LottoGame;