diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000..86680f82a6 Binary files /dev/null and b/.DS_Store differ diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..8ebc6d4973 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,81 @@ +# ๐Ÿ’Ž ๋กœ๋˜ ๐Ÿ’Ž + + + +## โœ”๏ธŽ ๊ธฐ๋Šฅ ๋ชฉ๋ก +* [E] : ์˜ˆ์™ธ ์ฒ˜๋ฆฌ -> throw ๋ฌธ ์‚ฌ์šฉ + + ex) [ERROR] ์ˆซ์ž๊ฐ€ ์ž˜๋ชป๋œ ํ˜•์‹์ž…๋‹ˆ๋‹ค. + +### (1) ๐Ÿ“ ๋กœ๋˜ ๊ตฌ์ž… ๊ธˆ์•ก ์ž…๋ ฅ (1,000์› ๋‹จ์œ„) +> [E] ์ˆซ์ž๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ + [E] 1,000์› ๋‹จ์œ„๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ + +* ex) + + ๊ตฌ์ž…๊ธˆ์•ก์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”. + 5000 + +### (2) ๐ŸŸก ๊ตฌ๋งค ๋กœ๋˜ ๋ฒˆํ˜ธ ์ถœ๋ ฅ +> (i) ๋กœ๋˜ (๊ตฌ์ž… ๊ธˆ์•ก / 1000)๊ฐœ ์ถœ๋ ฅ + (ii) ๋กœ๋˜ 1๊ฐœ๋‹น ๋žœ๋ค ์ˆซ์ž 6๊ฐœ์”ฉ ์ถœ๋ ฅ (์ค‘๋ณต X, ์ˆซ์ž ๋ฒ”์œ„ 1 ~ 45) + (iii) ์˜ค๋ฆ„์ฐจ์ˆœ์œผ๋กœ ์ •๋ ฌ ํ›„ ์ถœ๋ ฅ + +* ex) + + 5๊ฐœ๋ฅผ ๊ตฌ๋งคํ–ˆ์Šต๋‹ˆ๋‹ค. + [8, 21, 23, 41, 42, 43] + [3, 5, 11, 16, 32, 38] + [7, 11, 16, 35, 36, 44] + [1, 8, 11, 31, 41, 42] + [1, 3, 5, 14, 22, 45] + +### (3) ๐ŸŸข ๋‹น์ฒจ ๋ฒˆํ˜ธ ์ž…๋ ฅ (,๋กœ ๊ตฌ๋ถ„) +> [E] ์ˆซ์ž๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ + [E] ์ˆซ์ž๊ฐ€ 6๊ฐœ๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ + [E] ์ˆซ์ž ๋ฒ”์œ„๊ฐ€ 1 ~ 45 ๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ + [E] ์ค‘๋ณต๋œ ์ˆซ์ž๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ + +* ex) + + ๋‹น์ฒจ ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”. + 1,2,3,4,5,6 + +### (4) +๐ŸŸข ๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ ์ž…๋ ฅ +> [E] ์ˆซ์ž๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ + [E] ์ˆซ์ž ๋ฒ”์œ„๊ฐ€ 1 ~ 45 ๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ + [E] ๋‹น์ฒจ ๋ฒˆํ˜ธ(3)์™€ ์ค‘๋ณต๋œ ์ˆซ์ž๊ฐ€ ์žˆ๋Š” ๊ฒฝ์šฐ + +* ex) + + ๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”. + 7 + +### (5) ๐Ÿ“ ๋‹น์ฒจ ๋‚ด์—ญ ์ถœ๋ ฅ +> (i) 3๊ฐœ ์ผ์น˜ -> 5,000์› + (ii) 4๊ฐœ ์ผ์น˜ -> 50,000์› + +> (iii) 5๊ฐœ ์ผ์น˜ +>> ๋‚˜๋จธ์ง€ 1๊ฐœ๊ฐ€ ๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ๊ฐ€ ์•„๋‹Œ ๊ฒฝ์šฐ -> 1,500,000์› +>> ๋‚˜๋จธ์ง€ 1๊ฐœ๊ฐ€ ๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ์ธ ๊ฒฝ์šฐ -> 30,000,000์› + +> (iv) 6๊ฐœ ์ผ์น˜ -> 2,000,000,000์› + +* ex) + + ๋‹น์ฒจ ํ†ต๊ณ„ + --- + 3๊ฐœ ์ผ์น˜ (5,000์›) - 1๊ฐœ + 4๊ฐœ ์ผ์น˜ (50,000์›) - 0๊ฐœ + 5๊ฐœ ์ผ์น˜ (1,500,000์›) - 0๊ฐœ + 5๊ฐœ ์ผ์น˜, ๋ณด๋„ˆ์Šค ๋ณผ ์ผ์น˜ (30,000,000์›) - 0๊ฐœ + 6๊ฐœ ์ผ์น˜ (2,000,000,000์›) - 0๊ฐœ + +### (6) ๐Ÿ“ ์ˆ˜์ต๋ฅ  ์ถœ๋ ฅ +> (i) (์ด ๋‹น์ฒจ ๊ธˆ์•ก / ๊ตฌ์ž… ๊ธˆ์•ก) ์ถœ๋ ฅ + (ii) ์†Œ์ˆ˜์  ๋‘˜์งธ ์ž๋ฆฌ์—์„œ ๋ฐ˜์˜ฌ๋ฆผ + (iii) ์ฒœ ๋‹จ์œ„๋กœ , ํ‘œ์‹œ (ex. 2,000,000%) + +* ex) + + ์ด ์ˆ˜์ต๋ฅ ์€ 100.0%์ž…๋‹ˆ๋‹ค. diff --git a/package-lock.json b/package-lock.json index 779fba30f4..b58ea42cdc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -13,7 +13,7 @@ }, "devDependencies": { "@babel/core": "^7.23.0", - "@babel/preset-env": "^7.22.20", + "@babel/preset-env": "^7.23.2", "babel-jest": "^29.7.0", "jest": "29.6.0" }, diff --git a/package.json b/package.json index 80eb8de6af..9f2c163669 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,7 @@ }, "devDependencies": { "@babel/core": "^7.23.0", - "@babel/preset-env": "^7.22.20", + "@babel/preset-env": "^7.23.2", "babel-jest": "^29.7.0", "jest": "29.6.0" }, diff --git a/src/App.js b/src/App.js index c38b30d5b2..d613cc1f5e 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,100 @@ +import { MissionUtils } from '@woowacourse/mission-utils'; +import inputView from './InputView.js'; +import validation from './validation.js'; +import Lotto from './Lotto.js'; +import { LOTTO } from './constant.js'; +import outputView from './OutputView.js'; + class App { - async play() {} + async play() { + this.gameStart(); + } + + async gameStart() { + const purchasePrice = await inputView.purchaseInput(); + validation.checkPurchasePrice(purchasePrice); + + return(this.pickLottoNum(purchasePrice)); + } + + pickLottoNum(purchasePrice){ + const lottos = []; + const lottoCount = purchasePrice / LOTTO.UNIT; + + while (lottos.length < lottoCount) { + const lottoNums = arraySort(this.pickNum()); + lottos.push(new Lotto(lottoNums)); + } + + outputView.printLottoCount(lottoCount); + lottos.forEach((lotto) => outputView.printLottoNum(lotto)); + + return this.checkWinningNum(lottos); + } + + static arraySort(arr) { + return [...arr].sort((a, b) => a - b); + } + + pickNum() { + const lottoNum = MissionUtils.Random.pickUniqueNumbersInRange(LOTTO.MIN_RANGE, LOTTO.MAX_RANGE, LOTTO.LENGTH); + return lottoNum; + } + + async checkWinningNum(lottos) { + const winningNum = await inputView.winningNumInput(); + const winningNums = winningNum.split(','); + validation.checkWinningNum(winningNums); + + const bonusNum = await inputView.bonusNumInput(); + validation.checkBonusNum(winningNums, bonusNum); + + return this.countLottoResult({lottos, winningNums, bonusNum}); + } + + countLottoResult({lottos, winningNums, bonusNum}) { + const matchScore = []; + const hasBonusNum = []; + + lottos.forEach((lotto) => { + matchScore.push(lotto.getMatchCount(winningNums)); + if (lotto.hasBonusNumber(bonusNum)) + hasBonusNum.push(true); + else + hasBonusNum.push(false); + }); + + return this.countLottoRank({matchScore, hasBonusNum}); + } + + countLottoRank({matchScore, hasBonusNum}) { + const rank = [0, 0, 0, 0, 0, 0]; + for (let i = 0; i < matchScore.length; i++) { + if (matchScore[i] === 6) rank[1] += 1; + else if (matchScore[i] === 5 && hasBonusNum[i]) { + rank[2] += 1; + } else if (matchScore[i] === 5 && !hasBonusNum[i]) { + rank[3] += 1; + } else if (matchScore[i] === 4) { + rank[4] += 1; + } else if (matchScore[i] === 3) { + rank[5] += 1; + } + } + return this.calculateWinnings(rank); + } + + calculateWinnings(rank) { + let totalWin = 0; + totalWin += rank[1] * LOTTO.FIRST_PRIZE; + totalWin += rank[2] * LOTTO.SECOND_PRIZE; + totalWin += rank[3] * LOTTO.THIRD_PRIZE; + totalWin += rank[4] * LOTTO.FOURTH_PRIZE; + totalWin += rank[5] * LOTTO.FIFTH_PRIZE; + return this.calculateRateOfReturn({rank, totalWin}); + } + + } export default App; diff --git a/src/InputView.js b/src/InputView.js new file mode 100644 index 0000000000..2bd2be9143 --- /dev/null +++ b/src/InputView.js @@ -0,0 +1,21 @@ +import { MissionUtils } from '@woowacourse/mission-utils'; +import { INPUT_MSG } from './constant.js'; + +const inputView = { + async purchaseInput() { + const inputPurchase = await MissionUtils.Console.readLineAsync(INPUT_MSG.PURCHASE); + return inputPurchase; + }, + + async winningNumInput() { + const inputwinningNum = await MissionUtils.Console.readLineAsync(INPUT_MSG.WINNING_NUM); + return inputwinningNum; + }, + + async bonusNumInput() { + const inputbonusNum = await MissionUtils.Console.readLineAsync(INPUT_MSG.BONUS_NUM); + return inputbonusNum; + }, +}; + +export default inputView; \ No newline at end of file diff --git a/src/Lotto.js b/src/Lotto.js index cb0b1527e9..bcce648319 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -1,3 +1,6 @@ +import { ERROR_MSG } from './constant.js'; +import validation from './validation.js'; + class Lotto { #numbers; @@ -7,12 +10,21 @@ class Lotto { } #validate(numbers) { - if (numbers.length !== 6) { - throw new Error("[ERROR] ๋กœ๋˜ ๋ฒˆํ˜ธ๋Š” 6๊ฐœ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค."); - } + validation.checkLottoNum(numbers); + } + + getMatchCount(winningNum) { + let matchCount = 0; + winningNum.forEach((num) => { + if (this.#numbers.includes(num)) + matchCount += 1; + }); + return matchCount; } - // TODO: ์ถ”๊ฐ€ ๊ธฐ๋Šฅ ๊ตฌํ˜„ + hasBonusNumber(bonusNum) { + return this.#numbers.includes(bonusNum); + } } -export default Lotto; +export default Lotto; \ No newline at end of file diff --git a/src/OutputView.js b/src/OutputView.js new file mode 100644 index 0000000000..2014fd2420 --- /dev/null +++ b/src/OutputView.js @@ -0,0 +1,14 @@ +import { MissionUtils } from '@woowacourse/mission-utils'; +import { LOTTO, OUTPUT_MSG } from './constant.js'; + +const outputView = { + printLottoCount(lottoCount) { + MissionUtils.Console.print(OUTPUT_MSG.PURCHASE(lottoCount)); + }, + + printLottoNum(numbers) { + MissionUtils.Console.print(`[${numbers.join(', ')}]`); + }, +}; + +export default outputView; \ No newline at end of file diff --git a/src/constant.js b/src/constant.js new file mode 100644 index 0000000000..671e3033ba --- /dev/null +++ b/src/constant.js @@ -0,0 +1,40 @@ +export const LOTTO = { + UNIT: 1000, + LENGTH: 6, + MIN_RANGE: 1, + MAX_RANGE: 45, + + FIFTH_PRIZE: 5000, + FOURTH_PRIZE: 50000, + THIRD_PRIZE: 1500000, + SECOND_PRIZE: 30000000, + FIRST_PRIZE: 2000000000, +} + +export const INPUT_MSG = { + PURCHASE: '๊ตฌ์ž…๊ธˆ์•ก์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.\n', + WINNING_NUM: '๋‹น์ฒจ ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.\n', + BONUS_NUM: '๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.\n', +} + +export const OUTPUT_MSG = { + PURCHASE: (ea) => `${ea}๊ฐœ๋ฅผ ๊ตฌ๋งคํ–ˆ์Šต๋‹ˆ๋‹ค.\n`, + + TOTAL_WIN: '๋‹น์ฒจ ํ†ต๊ณ„\n---\n', + FIFTH: '3๊ฐœ ์ผ์น˜ (5,000์›) - ', + FOURTH: '4๊ฐœ ์ผ์น˜ (50,000์›) - ', + THIRD: '5๊ฐœ ์ผ์น˜ (1,500,000์›) - ', + SECOND: '5๊ฐœ ์ผ์น˜, ๋ณด๋„ˆ์Šค ๋ณผ ์ผ์น˜ (30,000,000์›) - ', + FIRST: '6๊ฐœ ์ผ์น˜ (2,000,000,000์›) - ', + EA: '๊ฐœ\n', + + TOTAL_RETURN: (rate) => `์ด ์ˆ˜์ต๋ฅ ์€ ${rate}%์ž…๋‹ˆ๋‹ค.\n`, +} + +export const ERROR_MSG = { + INPUT_NAN: '[ERROR] ์ž…๋ ฅ๋œ ์ˆซ์ž๊ฐ€ ์ž˜๋ชป๋œ ํ˜•์‹์ž…๋‹ˆ๋‹ค.', + NUM_UNIT: '[ERROR] 1,000์› ๋‹จ์œ„๋กœ๋งŒ ์ž…๋ ฅ ๊ฐ€๋Šฅํ•ฉ๋‹ˆ๋‹ค.', + NUM_RANGE: '[ERROR] ๋กœ๋˜ ๋ฒˆํ˜ธ๋Š” 1๋ถ€ํ„ฐ 45 ์‚ฌ์ด์˜ ์ˆซ์ž์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.', + NUM_LENGTH: '[ERROR] ๋กœ๋˜ ๋ฒˆํ˜ธ๋Š” 6๊ฐœ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.', + NUM_DUPE: '[ERROR] ์ค‘๋ณต๋œ ์ˆซ์ž๋Š” ์ž…๋ ฅํ•  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.', +}; \ No newline at end of file diff --git a/src/validation.js b/src/validation.js new file mode 100644 index 0000000000..f75e5afcad --- /dev/null +++ b/src/validation.js @@ -0,0 +1,60 @@ +import { LOTTO, ERROR_MSG } from './constant.js'; + +const validation = { + checkPurchasePrice(input) { + if (isNaN(input)) + throw new Error(ERROR_MSG.INPUT_NAN); + + if (input / LOTTO.UNIT !== 0) + throw new Error(ERROR_MSG.NUM_UNIT); + + return 0; + }, + + checkLottoNum(input) { + input.forEach((num) => { + if (num < LOTTO.MIN_RANGE || num > LOTTO.MAX_RANGE) + throw new Error(ERROR_MSG.NUM_RANGE); + }); + if (input.length !== LOTTO.LENGTH) + throw new Error(ERROR_MSG.NUM_LENGTH); + + if (new Set(input).size !== input.length) + throw new Error(ERROR_MSG.NUM_DUPE); + + return 0; + }, + + checkWinningNum(input) { + input.forEach((num) => { + if (isNaN(num)) + throw new Error(ERROR_MSG.INPUT_NAN); + + if (num < LOTTO.MIN_RANGE || num > LOTTO.MAX_RANGE) + throw new Error(ERROR_MSG.NUM_RANGE); + }); + if (input.length !== LOTTO.LENGTH) + throw new Error(ERROR_MSG.NUM_LENGTH); + + if (new Set(input).size !== input.length) + throw new Error(ERROR_MSG.NUM_DUPE); + + return 0; + }, + + checkBonusNum(winning, bonus) { + if (isNaN(bonus)) + throw new Error(ERROR_MSG.INPUT_NAN); + + if (bonus < LOTTO.MIN_RANGE || bonus > LOTTO.MAX_RANGE) + throw new Error(ERROR_MSG.NUM_RANGE); + + const arr = winning.push(bonus); + if (new Set(arr).size !== arr.length) + throw new Error(ERROR_MSG.NUM_DUPE); + + return 0; + }, + }; + + export default validation; \ No newline at end of file