Skip to content
Open
76 changes: 76 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,77 @@
# javascript-planetlotto-precourse

### 구현 기능 목록
각 기능 구현이 완료될 때마다 AngularJS 커밋 컨벤션을 사용하여 커밋.
커밋 단위는 “한 기능 = 한 커밋”


### 1. 구입 금액 입력 받기

- [x] InputView.askAmount로 구매 금액 입력 받기
- [x] 구입 금액이 500으로 나누어 떨어지는 양의 정수인지, 500 이상의 값인지 확인

---

### 2. 구매한 로또 개수 및 번호 출력하기

- [x] OutputView.printPurchasedLottos를 이용해 구매한 로또 개수와 로또 출력

---

### 3. 당첨 번호 입력 하기

- [x] InputView.askWinningLotto로 당첨 번호 입력

---

### 4. 보너스 번호 입력 하기

- [x] InputView.askBonusNumber로 보너스 번호 입력

---

### 5. countsByRank 계산하기

- [x] rank별로 일치하는 로또가 몇 개 있는지 계산

---

### 6. 당첨 결과 출력

- [x] OutputView.printResult로 당첨 결과 출력

---

### 7. 에러 처리 확인하기

- [x] 에러 지점에서 Output.printErrorMessage로 에러 출력
- [x] ApplicationTest.js 통과


---

### 도전 과제(리팩터링)
- [ ] view 부분을 제외하고 나머지 코드의 가독성을 향상 시키고 중복되는 부분을 제거할 것
- [x] MVC 구조에 기반하여 코드 재구성하기
- [x] 한 가지 함수에 하나의 기능을 담을 것
- [x] 상수는 constants에서 관리하여 사용하기
- [ ] 에러 발생 후 재입력 받기


---


#### 출력 결과 예시
```text
당첨 통계
---
5개 일치 (100,000,000원) - 0개
4개 일치, 보너스 번호 일치 (10,000,000원) - 0개
4개 일치 (1,500,000원) - 0개
3개 일치, 보너스 번호 일치 (500,000원) - 0개
2개 일치, 보너스 번호 일치 (5,000원) - 1개
0개 일치 (0원) - 1개

---


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 "./controller/LottoController.js";

class App {
async run() {}
async run() {
const lottoController = new LottoController();
await lottoController.run()
}
}

export default App;
3 changes: 3 additions & 0 deletions src/constants/ConstantValues.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export const CONSTANT_VALUES = {
ONE_LOTTO_PRICE: 500,
}
8 changes: 8 additions & 0 deletions src/constants/ErrorMessages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const LOTTO_CONSTANTS = {
INVALID_VALUE: "[ERROR] 정수값을 입력해주시길 바랍니다.",
MIN_PURCHASE: "[ERROR] 최소 금액은 500원 입니다.",
WINNING_NUMBER_NOT_IN_RANGE: "[ERROR] 1~30 사이의 정수를 입력해주시길 바랍니다.",
BONUS_NUMBER_NOT_INTEGER: "[ERROR] 보너스 번호의 값은 정수여야 합니다.",
BONUS_NUMBER_OUT_OF_RANGE: "[ERROR] 1에서 45사이의 값을 입력해주시길 바랍니다.",
BONUS_NUMBER_DUPLICATE: "[ERROR] 보너스 번호가 당첨 번호와 중복됩니다.",
}
8 changes: 8 additions & 0 deletions src/constants/ranks.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export const RANKS = [
{ rank: 1, matchingCount: 5, hasBonusNumber: false },
{ rank: 2, matchingCount: 4, hasBonusNumber: true },
{ rank: 3, matchingCount: 4, hasBonusNumber: false },
{ rank: 4, matchingCount: 3, hasBonusNumber: false },
{ rank: 5, matchingCount: 2, hasBonusNumber: false },
{ rank: 0, matchingCount: 0, hasBonusNumber: false },
];
23 changes: 23 additions & 0 deletions src/controller/LottoController.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import { LottoGenerator } from "../model/LottoGenerator.js";
import { LottoQuantity } from "../model/LottoQuantity.js";
import { LottoResultCalculator } from "../model/LottoResultCalculator.js";
import { Validator } from "../validator/Validator.js";
import { InputView, OutputView } from "../view.js";

export default class LottoController {
async run() {
const lottoPurchaseAmount = await InputView.askAmount();
if(lottoPurchaseAmount == "ERROR" || Validator.validateMoneyPurchased(lottoPurchaseAmount) == "ERROR"){
const lottoPurchaseAmount = await InputView.askAmount();
}
const lottoQuantity = LottoQuantity.calculateLottoQuantity(lottoPurchaseAmount);
const lottos = LottoGenerator.generateLotto(lottoQuantity);
OutputView.printPurchasedLottos(lottos)
const winningNumbers = await InputView.askWinningLotto();
Validator.validateWinningNumbers(winningNumbers)
const bonusNumber = await InputView.askBonusNumber();
Validator.validateBonusNumber(bonusNumber,winningNumbers)
const countsByRank = LottoResultCalculator.calculateRankResults(lottos,winningNumbers,bonusNumber);
OutputView.printResult(countsByRank);
}
}
14 changes: 14 additions & 0 deletions src/model/LottoGenerator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Random } from "@woowacourse/mission-utils";

export class LottoGenerator {
static generateLotto(lottoQuantity){
const lottoArray = Array.from(
{length:lottoQuantity},
() => {
const numbers = Random.pickUniqueNumbersInRange(1,30,5).sort((a, b) => a - b);
return numbers;
}
);
return lottoArray;
}
}
7 changes: 7 additions & 0 deletions src/model/LottoQuantity.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import { CONSTANT_VALUES } from "../constants/ConstantValues";

export class LottoQuantity{
static calculateLottoQuantity(lottoPurchaseAmount){
return lottoPurchaseAmount / CONSTANT_VALUES.ONE_LOTTO_PRICE;
}
}
41 changes: 41 additions & 0 deletions src/model/LottoResultCalculator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
import RankFinder from "./RankFinder.js";

export class LottoResultCalculator {
static calculateRankResults(lottos,winningLotto,bonusNumber){
const lottoRankResults = { 1: 0, 2: 0, 3: 0, 4: 0, 5: 0, 0 : 0};

lottos.forEach((lotto)=>{
this.updateRankResult(lotto, winningLotto, bonusNumber, lottoRankResults);
});
const countsByRank = new Map();
for (const key in lottoRankResults){
countsByRank.set(Number(key),lottoRankResults[key]);
}
return countsByRank;
}
static updateRankResult(lotto, winningLotto, bonusNumber, lottoRankResults){
const matchingCount = this.getMatchingCount(lotto, winningLotto);
const hasBonusNumber = this.hasBonusNumber(lotto, bonusNumber);
const rank = RankFinder.getRank(matchingCount,hasBonusNumber);
if(!rank){
return;
}
this.addRankCount(lottoRankResults, rank.rank)
}

static addRankCount(lottoRankResults, rank) {
lottoRankResults[rank] += 1;
}

static getMatchingCount(lotto, winningLotto) {
const winningNumbers = winningLotto
const matchingCount = winningNumbers.filter((winningNumber) =>
lotto.includes(winningNumber),
);

return matchingCount.length;
}
static hasBonusNumber(lotto, bonusNumber) {
return lotto.includes(bonusNumber);
}
}
17 changes: 17 additions & 0 deletions src/model/RankFinder.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { RANKS } from "../constants/ranks.js";

export default class RankFinder {
static getRank(matchingCount, hasBonusNumber) {
if (matchingCount === 4) {
const secondRank = RANKS.find(
(rank) => rank.matchingCount === matchingCount && rank.hasBonusNumber === hasBonusNumber,
);

return secondRank;
}

const rank = RANKS.find((rank) => rank.matchingCount === matchingCount);

return rank;
}
}
45 changes: 45 additions & 0 deletions src/validator/Validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { CONSTANT_VALUES } from "../constants/ConstantValues.js";
import { LOTTO_CONSTANTS } from "../constants/errorMessages.js";
import { OutputView } from "../view.js";

export class Validator {
static validateMoneyPurchased(moneyPurchased){
if(Number.isNaN(moneyPurchased)){
OutputView.printErrorMessage("숫자가 아닌 값이 입력됐습니다. 숫자를 입력해주세요.")
return "ERROR"
}
if(moneyPurchased<CONSTANT_VALUES.ONE_LOTTO_PRICE){
OutputView.printErrorMessage("500 이상의 금액을 입력해주세요.")
return "ERROR"
}
if(!Number.isInteger(moneyPurchased / CONSTANT_VALUES.ONE_LOTTO_PRICE)){
OutputView.printErrorMessage("500 배수의 금액을 입력해주세요.")
return "ERROR"
}
return "PASS";
}

static validateWinningNumbers(winningNumbers) {
if(winningNumbers.some(number=>number<1||number>30)){
throw new Error(LOTTO_CONSTANTS.WINNING_NUMBER_NOT_IN_RANGE);
}
}

static validateBonusNumber(bonusNumber, winningNumbers) {
if (Number.isNaN(bonusNumber)) {
throw new Error(LOTTO_CONSTANTS.INVALID_VALUE);
}

if (!Number.isInteger(bonusNumber)) {
throw new Error(LOTTO_CONSTANTS.BONUS_NUMBER_NOT_INTEGER);
}

if (!(bonusNumber <= 30 && bonusNumber >= 1)) {
throw new Error(LOTTO_CONSTANTS.BONUS_NUMBER_OUT_OF_RANGE);
}

if (winningNumbers.includes(bonusNumber)) {
throw new Error(LOTTO_CONSTANTS.BONUS_NUMBER_DUPLICATE);
}
}
}
6 changes: 6 additions & 0 deletions src/view.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,16 @@ const InputView = {
*/
async askAmount() {
const input = await MissionUtils.Console.readLineAsync('구입금액을 입력해 주세요.\n');
if(Number.isNaN(Number(input)) == true){
OutputView.printErrorMessage("구매금액은 숫자여야 합니다.")
return "ERROR"
}

const num = parseInt(input, 10);
if (Number.isNaN(num)) {
throw new Error('구매금액은 숫자여야 합니다.');
}

return num;
},

Expand Down