diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000000..9bbb5b15f6 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,70 @@ +# ๋ฏธ์…˜ - ๋กœ๋˜ + +## ๐ŸŽฏ ๋ชฉํ‘œ + +- [ ] ํด๋ž˜์Šค(๊ฐ์ฒด)๋ฅผ ๋ถ„๋ฆฌํ•˜๋Š” ์—ฐ์Šต +- [ ] ๋„๋ฉ”์ธ ๋กœ์ง์— ๋Œ€ํ•œ ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ์ž‘์„ฑํ•˜๋Š” ์—ฐ์Šต + +## ๐Ÿ•น ๊ธฐ๋Šฅ ์š”๊ตฌ ์‚ฌํ•ญ(๊ตฌํ˜„ํ•  ๊ธฐ๋Šฅ ๋ชฉ๋ก) + +### โŒจ๏ธ ์‚ฌ์šฉ์ž ์ž…๋ ฅ ๋ฐ›๊ธฐ + +- [x] ๋กœ๋˜ ๊ตฌ์ž… ๊ธˆ์•ก์„ ์ž…๋ ฅ ๋ฐ›๊ธฐ +- [x] ๋‹น์ฒจ ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅ ๋ฐ›๊ธฐ +- [x] ๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅ ๋ฐ›๊ธฐ + +#### ์˜ˆ์™ธ : "[ERROR]"๋กœ ์‹œ์ž‘ + +- [x] ์ˆซ์ž ์™ธ์˜ ๊ฒƒ์„ ์ž…๋ ฅํ•  ์‹œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ +- [x] 1,000์›์œผ๋กœ ๋‚˜๋ˆ„์–ด ๋–จ์–ด์ง€์ง€ ์•Š์„ ์‹œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ +- ~~[ ] ์‰ผํ‘œ(,)๋กœ ๊ตฌ๋ถ„ํ•˜์ง€ ์•Š์„ ์‹œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ~~ +- [x] ๋กœ๋˜ ๋ฒˆํ˜ธ ์ž…๋ ฅ๋ฐ›์„ ๋•Œ 1~45์˜ ์ˆซ์ž ๋ฒ”์œ„๋ฅผ ๋ฒ—์–ด๋‚  ์‹œ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ + +### ๐Ÿ–จ ์ถœ๋ ฅ + +- [x] ๋ฐœํ–‰ํ•œ ๋กœ๋˜ ์ˆ˜๋Ÿ‰ ์ถœ๋ ฅ +- [x] ๋ฐœํ–‰ํ•œ ๋กœ๋˜ ๋ฒˆํ˜ธ ์ถœ๋ ฅ + - [x] ๋กœ๋˜ ๋ฒˆํ˜ธ๋Š” ์˜ค๋ฆ„์ฐจ์ˆœ์œผ๋กœ ์ •๋ ฌ + +#### ๐ŸŽฎ ๊ฒŒ์ž„ ์ข…๋ฃŒ + +- [x] ๋‹น์ฒจ ๋‚ด์—ญ์„ ์ถœ๋ ฅ : ๋‹น์ฒจ ๊ธฐ์ค€์— ๋”ฐ๋ฅธ ๋‹น์ฒจ๋œ ๋กœ๋˜ ๊ฐœ์ˆ˜ ์ถœ๋ ฅ +- [x] ์ด ์ˆ˜์ต๋ฅ  ์ถœ๋ ฅ + - [x] ์†Œ์ˆ˜์  ๋‘˜์งธ ์ž๋ฆฌ์—์„œ ๋ฐ˜์˜ฌ๋ฆผ + +### ๐ŸŽฐ ๊ฒŒ์ž„ ํŒ๋‹จ + +- [x] ์‚ฌ์šฉ์ž๊ฐ€ ๊ตฌ๋งคํ•œ ๋กœ๋˜ ๋ฒˆํ˜ธ์™€ ๋‹น์ฒจ ๋ฒˆํ˜ธ ๋น„๊ต + +## ๐Ÿ–ฅ ํ”„๋กœ๊ทธ๋ž˜๋ฐ ์š”๊ตฌ ์‚ฌํ•ญ + +- [x] `Node.js` `18.17.1` ๋ฒ„์ „์—์„œ ์‹คํ–‰ ๊ฐ€๋Šฅ +- [x] `App.js`์˜ `play` ๋ฉ”์„œ๋“œ์—์„œ ํ”„๋กœ๊ทธ๋žจ ์‹คํ–‰ +- [ ] `ApplicationTest` ์˜ ๋ชจ๋“  ํ…Œ์ŠคํŠธ ์„ฑ๊ณต +- [x] indent(์ธ๋ดํŠธ, ๋“ค์—ฌ์“ฐ๊ธฐ) depth๋ฅผ 3์ด ๋„˜์ง€ ์•Š๋„๋ก ๊ตฌํ˜„ +- [ ] Jest๋ฅผ ์ด์šฉํ•˜์—ฌ ๋‚ด๊ฐ€ ์ •๋ฆฌํ•œ ๊ธฐ๋Šฅ ๋ชฉ๋ก์ด ์ •์ƒ ๋™์ž‘ํ•จ์„ ํ…Œ์ŠคํŠธ ์ฝ”๋“œ๋กœ ํ™•์ธ + +### ์ถ”๊ฐ€ ์š”๊ตฌ ์‚ฌํ•ญ + +- [ ] ํ•จ์ˆ˜(๋˜๋Š” ๋ฉ”์„œ๋“œ)์˜ ๊ธธ์ด๊ฐ€ 15๋ผ์ธ์„ ๋„˜์–ด๊ฐ€์ง€ ์•Š๋„๋ก ๊ตฌํ˜„ + - [ ] ํ•จ์ˆ˜(๋˜๋Š” ๋ฉ”์„œ๋“œ)๊ฐ€ ํ•œ ๊ฐ€์ง€ ์ผ๋งŒ ํ•˜๋„๋ก ๊ตฌํ˜„ +- else๋ฅผ ์ง€์–‘ + (ํžŒํŠธ: if ์กฐ๊ฑด์ ˆ์—์„œ ๊ฐ’์„ returnํ•˜๋Š” ๋ฐฉ์‹์œผ๋กœ ๊ตฌํ˜„ํ•˜๋ฉด else๋ฅผ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•„๋„ ๋จ. + ๋•Œ๋กœ๋Š” if/else, switch๋ฌธ์„ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ์ด ๋” ๊น”๋”ํ•ด ๋ณด์ผ ์ˆ˜ ์žˆ์Œ.) +- [ ] ๋„๋ฉ”์ธ ๋กœ์ง์— ๋‹จ์œ„ ํ…Œ์ŠคํŠธ๋ฅผ ๊ตฌํ˜„ + - [ ] ํ•ต์‹ฌ ๋กœ์ง์„ ๊ตฌํ˜„ํ•˜๋Š” ์ฝ”๋“œ์™€ UI๋ฅผ ๋‹ด๋‹นํ•˜๋Š” ๋กœ์ง์„ ๊ตฌ๋ถ„ + +### ๋ผ์ด๋ธŒ๋Ÿฌ๋ฆฌ + +- [x] `@woowacourse/mission-utils`์—์„œ ์ œ๊ณตํ•˜๋Š” `Random` ๋ฐ `Console` API๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๊ตฌํ˜„ + +### Lotto ํด๋ž˜์Šค + +- [x] ์ œ๊ณต๋œ `Lotto` ํด๋ž˜์Šค๋ฅผ ํ™œ์šฉํ•ด ๊ตฌํ˜„ +- [x] `numbers`์˜ `#` prefix๋ฅผ ๋ณ€๊ฒฝํ•  ์ˆ˜ ์—†์Œ +- [x] `Lotto`์— ํ•„๋“œ๋ฅผ ์ถ”๊ฐ€ํ•  ์ˆ˜ ์—†์Œ + +## ๐Ÿ““ ๊ณผ์ œ ์ง„ํ–‰ ์š”๊ตฌ ์‚ฌํ•ญ + +- [x] ์ €์žฅ์†Œ๋ฅผ Fork & Clone +- [x] ๊ตฌํ˜„ํ•  ๊ธฐ๋Šฅ ๋ชฉ๋ก ์ž‘์„ฑ diff --git a/src/App.js b/src/App.js index c38b30d5b2..a7feaeb107 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,169 @@ +import { Random, Console } from "@woowacourse/mission-utils"; +import { GAME, ERROR } from "./message"; +import Lotto from "./Lotto"; + class App { - async play() {} + #amount; + #lottos; + + constructor() { + this.#amount = 0; + this.#lottos = []; + } + + validateAmount = (amount) => { + if (isNaN(amount) || amount < 0) { + throw new Error(ERROR.NOT_NUMBER); + } + if (amount % 1000 !== 0) { + throw new Error(ERROR.INVALID_AMOUNT); + } + }; + + validateWinningNumbers = (numbers) => { + if (numbers.length !== 6) { + throw new Error(ERROR.INVALID_ARRAY_LENGTH); + } + + for (const number of numbers) { + if (number < 1 || number > 45) { + throw new Error(ERROR.OUT_OF_RANGE); + } + } + + const uniqueNumbers = new Set(numbers); + if (uniqueNumbers.size !== numbers.length) { + throw new Error(ERROR.DUPLICATE_NUMBER); + } + }; + + validateBonusNumber = (number, winningNumbers) => { + if (isNaN(number) || number < 0) { + throw new Error(ERROR.NOT_NUMBER); + } + if (number < 1 || number > 45) { + throw new Error(ERROR.OUT_OF_RANGE); + } + if (winningNumbers.includes(number)) { + throw new Error(ERROR.DUPLICATE_NUMBER); + } + }; + + inputPurchaseAmount = async () => { + while (true) { + try { + this.#amount = Number(await Console.readLineAsync(GAME.INPUT.AMOUNT)); + this.validateAmount(this.#amount); + const count = this.#amount / 1000; + this.generateLottosList(count); + break; + } catch (error) { + throw new Error(ERROR.INPUT); + } + } + }; + + generateLottosList = (count) => { + Console.print(`${count}๊ฐœ๋ฅผ ๊ตฌ์ž…ํ–ˆ์Šต๋‹ˆ๋‹ค.`); + + for (let i = 0; i < count; i += 1) { + const numbers = Random.pickUniqueNumbersInRange(1, 45, 6); + this.#lottos.push(numbers); + const lotto = new Lotto(numbers); + lotto.printLottos(); + } + }; + + inputWinningNumbers = async () => { + while (true) { + try { + const numbers = await Console.readLineAsync(GAME.INPUT.WINNING_NUMBER); + const numbersArray = numbers + .split(",") + .map((num) => Number(num.trim())); + this.validateWinningNumbers(numbersArray); + return numbersArray; + } catch (error) { + throw new Error(ERROR.INPUT); + } + } + }; + + inputBonusNumber = async (winningNumbers) => { + while (true) { + try { + const number = Number( + await Console.readLineAsync(GAME.INPUT.BONUS_NUMBER) + ); + this.validateBonusNumber(number, winningNumbers); + return number; + } catch (error) { + throw new Error(ERROR.INPUT); + } + } + }; + + matchedNumbers = async () => { + const winningNumbers = await this.inputWinningNumbers(); + const bonusNumber = await this.inputBonusNumber(winningNumbers); + + let matched = [0, 0, 0, 0, 0]; + + this.#lottos.forEach((lotto) => { + const matchedCount = lotto.filter((number) => + winningNumbers.includes(number) + ).length; + + if (matchedCount === 6) matched[4] += 1; + else if (matchedCount === 5 && lotto.includes(bonusNumber)) { + matched[3] += 1; + } else if (matchedCount === 5) matched[2] += 1; + else if (matchedCount === 4) matched[1] += 1; + else if (matchedCount === 3) matched[0] += 1; + }); + + return matched; + }; + + winningStatistics = async () => { + const matchedCounts = await this.matchedNumbers(); + this.printWinningStatistics(matchedCounts); + this.calculateProfitRate(matchedCounts); + }; + + printWinningStatistics = (matchedCounts) => { + Console.print(GAME.WINNING_STATISTICS); + + const prizeMoney = [ + "5,000", + "50,000", + "1,500,000", + "30,000,000", + "2,000,000,000", + ]; + for (let i = 0; i < 5; i += 1) { + Console.print( + `${i + 3}๊ฐœ ์ผ์น˜ (${prizeMoney[i]}์›) - ${matchedCounts[i]}๊ฐœ` + ); + } + }; + + calculateProfitRate = (matchedCounts) => { + const prizeMoney = [5000, 50000, 1500000, 30000000, 2000000000]; + let totalCost = 0; + + for (let i = 0; i < 5; i += 1) { + totalCost += prizeMoney[i] * matchedCounts[i]; + } + const profitRate = ((totalCost / this.#amount) * 100).toFixed(1); + + Console.print(`์ด ์ˆ˜์ต๋ฅ ์€ ${profitRate}%์ž…๋‹ˆ๋‹ค.`); + }; + + async play() { + await this.inputPurchaseAmount(); + await this.winningStatistics(); + } } export default App; diff --git a/src/Lotto.js b/src/Lotto.js index cb0b1527e9..01a5f2aeee 100644 --- a/src/Lotto.js +++ b/src/Lotto.js @@ -1,3 +1,6 @@ +import { Console } from "@woowacourse/mission-utils"; +import { ERROR } from "./message"; + class Lotto { #numbers; @@ -8,11 +11,30 @@ class Lotto { #validate(numbers) { if (numbers.length !== 6) { - throw new Error("[ERROR] ๋กœ๋˜ ๋ฒˆํ˜ธ๋Š” 6๊ฐœ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค."); + throw new Error(ERROR.INVALID_ARRAY_LENGTH); + } + + for (const number of numbers) { + if (number < 1 || number > 45) { + throw new Error(ERROR.OUT_OF_RANGE); + } + } + + const uniqueNumbers = new Set(numbers); + if (uniqueNumbers.size !== numbers.length) { + throw new Error(ERROR.DUPLICATE_NUMBER); } } // TODO: ์ถ”๊ฐ€ ๊ธฐ๋Šฅ ๊ตฌํ˜„ + sortAscending = () => { + this.#numbers.sort((a, b) => a - b); + }; + + printLottos = () => { + this.sortAscending(); + Console.print(`[${this.#numbers.join(", ")}]`); + }; } export default Lotto; diff --git a/src/message.js b/src/message.js new file mode 100644 index 0000000000..9965032b4e --- /dev/null +++ b/src/message.js @@ -0,0 +1,17 @@ +export const GAME = Object.freeze({ + INPUT: { + AMOUNT: "๊ตฌ์ž…๊ธˆ์•ก์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.\n", + WINNING_NUMBER: "๋‹น์ฒจ ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”\n", + BONUS_NUMBER: "๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”.\n", + }, + WINNING_STATISTICS: "๋‹น์ฒจ ํ†ต๊ณ„\n---\n", +}); + +export const ERROR = Object.freeze({ + INPUT: "[ERROR] ์ž…๋ ฅ์„ ์‹คํŒจํ•˜์˜€์Šต๋‹ˆ๋‹ค.", + NOT_NUMBER: "[ERROR] 0 ์ด์ƒ์˜ ์ž์—ฐ์ˆ˜๋กœ ์ž…๋ ฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + INVALID_AMOUNT: "[ERROR] ๊ตฌ์ž… ๊ธˆ์•ก์€ 1000์› ๋‹จ์œ„๋กœ ์ž…๋ ฅํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + INVALID_ARRAY_LENGTH: "[ERROR] ๋กœ๋˜ ๋ฒˆํ˜ธ๋Š” 6๊ฐœ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + OUT_OF_RANGE: "[ERROR] ๋กœ๋˜ ๋ฒˆํ˜ธ๋Š” 1๋ถ€ํ„ฐ 45 ์‚ฌ์ด์˜ ์ˆซ์ž์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.", + DUPLICATE_NUMBER: "[ERROR] ์ค‘๋ณต๋œ ๋ฒˆํ˜ธ๊ฐ€ ํฌํ•จ๋˜์–ด ์žˆ์Šต๋‹ˆ๋‹ค", +});