diff --git a/.prettierrc b/.prettierrc new file mode 100644 index 00000000..5c5c48aa --- /dev/null +++ b/.prettierrc @@ -0,0 +1,9 @@ +{ + "arrowParens": "always", + "singleQuote": true, + "semi": true, + "useTabs": false, + "tabWidth": 2, + "printWidth": 80, + "trailingComma": "all" +} diff --git a/README.md b/README.md index b168a180..84bb0f58 100644 --- a/README.md +++ b/README.md @@ -1 +1,99 @@ # javascript-planetlotto-precourse + +## ๐Ÿ“Œ ๊ฐœ์š” + +- ์šฐํ…Œ์ฝ” ๋กœ๋˜ ๋ฐœ๋งค๊ธฐ์ธ ํ–‰์„ฑ ๋กœ๋˜๋ฅผ ๊ตฌํ˜„ํ•œ๋‹ค + +## โŒจ๏ธ ์ž…๋ ฅ + +- ๋กœ๋˜ ๊ตฌ์ž… ๊ธˆ์•ก์„ ์ž…๋ ฅ๋ฐ›๋Š”๋‹ค. + +``` +1000 + +``` + +- ๋‹น์ฒจ ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅ๋ฐ›๋Š”๋‹ค. ๋ฒˆํ˜ธ๋Š” ์‰ผํ‘œ(,)๋ฅผ ๊ธฐ์ค€์œผ๋กœ ๊ตฌ๋ถ„ํ•œ๋‹ค. + +``` +1,2,3,4,5 + +``` + +- ๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅ๋ฐ›๋Š”๋‹ค. + +``` +6 + +``` + +## ๐Ÿ–ฅ ์ถœ๋ ฅ + +- ๋ฐœํ–‰ํ•œ ๋กœ๋˜ ์ˆ˜๋Ÿ‰ ๋ฐ ๋ฒˆํ˜ธ๋ฅผ ์ถœ๋ ฅํ•œ๋‹ค. ๋กœ๋˜ ๋ฒˆํ˜ธ๋Š” ์˜ค๋ฆ„์ฐจ์ˆœ์œผ๋กœ ์ •๋ ฌํ•˜์—ฌ ๋ณด์—ฌ์ค€๋‹ค. + +``` +2๊ฐœ๋ฅผ ๊ตฌ๋งคํ–ˆ์Šต๋‹ˆ๋‹ค. +[8, 11, 13, 21, 22] +[1, 3, 6, 14, 22] +``` + +- ๋‹น์ฒจ ๋‚ด์—ญ์„ ์ถœ๋ ฅํ•œ๋‹ค. + +``` +๋‹น์ฒจ ํ†ต๊ณ„ +--- +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๊ฐœ +``` + +- ์˜ˆ์™ธ ์ƒํ™ฉ ์‹œ ์—๋Ÿฌ ๋ฌธ๊ตฌ๋ฅผ ์ถœ๋ ฅํ•ด์•ผ ํ•œ๋‹ค. ๋‹จ, ์—๋Ÿฌ ๋ฌธ๊ตฌ๋Š” "[ERROR]"๋กœ ์‹œ์ž‘ํ•ด์•ผ ํ•œ๋‹ค. + +``` +[ERROR] ๋กœ๋˜ ๋ฒˆํ˜ธ๋Š” 1๋ถ€ํ„ฐ 30 ์‚ฌ์ด์˜ ์ˆซ์ž์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค. +``` + +## ์‹คํ–‰ ๊ฒฐ๊ณผ ์˜ˆ์‹œ + +``` +๊ตฌ์ž…๊ธˆ์•ก์„ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”. +1000 + +2๊ฐœ๋ฅผ ๊ตฌ๋งคํ–ˆ์Šต๋‹ˆ๋‹ค. +[8, 11, 13, 21, 22] +[1, 3, 6, 14, 22] + +๋‹น์ฒจ ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”. +1, 2, 3, 4, 5 + +๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅํ•ด ์ฃผ์„ธ์š”. +6 + +๋‹น์ฒจ ํ†ต๊ณ„ +--- +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๊ฐœ +``` + +## ๐Ÿงฉ ๊ตฌํ˜„ ๊ธฐ๋Šฅ ๋ชฉ๋ก + +- ๋กœ๋˜ ๊ตฌ์ž… ๊ธˆ์•ก์„ ์ž…๋ ฅ ๋ฐ›๋Š”๋‹ค + - ๋กœ๋˜ 1์žฅ์˜ ๊ฐ€๊ฒฉ์€ 500์›์ด๋‹ค +- ๋‹น์ฒจ ๋ฒˆํ˜ธ์™€ ๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ๋ฅผ ์ž…๋ ฅ๋ฐ›๋Š”๋‹ค +- ์‚ฌ์šฉ์ž๊ฐ€ ๊ตฌ๋งคํ•œ ๋กœ๋˜ ๋ฒˆํ˜ธ์™€ ๋‹น์ฒจ ๋ฒˆํ˜ธ๋ฅผ ๋น„๊ตํ•œ๋‹ค +- ๋‹น์ฒจ ๋‚ด์—ญ์„ ์ถœ๋ ฅํ•œ๋‹ค + +## โš ๏ธ ์˜ˆ์™ธ ์ฒ˜๋ฆฌ + +- + +## โž• ๊ธฐํƒ€ + +- ์ถ”๊ฐ€ ์กฐ๊ฑด diff --git a/src/App.js b/src/App.js index 091aa0a5..bcba0f2c 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,9 @@ +import LottoController from './controller/LottoController.js'; + class App { - async run() {} + async run() { + const manager = new LottoController(); + await manager.runLottoMachine(); + } } - export default App; diff --git a/src/constants/format.js b/src/constants/format.js new file mode 100644 index 00000000..b93d88c2 --- /dev/null +++ b/src/constants/format.js @@ -0,0 +1,20 @@ +export const LOTTO_CONSTANTS = { + PRICE: 500, + MIN_NUMBER: 1, + MAX_NUMBER: 30, + NUMBER_COUNT: 5, +}; +export const RANK = { + FIRST: 'FIRST', + SECOND: 'SECOND', + THIRD: 'THIRD', + FOURTH: 'FOURTH', + FIFTH: 'FIFTH', +}; +export const PRICE_INFO = { + [RANK.FIFTH]: { key: 5, prize: 5000, text: '3๊ฐœ ์ผ์น˜' }, + [RANK.FOURTH]: { key: 4, prize: 50000, text: '4๊ฐœ ์ผ์น˜' }, + [RANK.THIRD]: { key: 3, prize: 1500000, text: '5๊ฐœ ์ผ์น˜' }, + [RANK.SECOND]: { key: 2, prize: 30000000, text: '5๊ฐœ ์ผ์น˜, ๋ณด๋„ˆ์Šค ๋ณผ ์ผ์น˜' }, + [RANK.FIRST]: { key: 1, prize: 2000000000, text: '6๊ฐœ ์ผ์น˜' }, +}; diff --git a/src/constants/message.js b/src/constants/message.js new file mode 100644 index 00000000..17475e71 --- /dev/null +++ b/src/constants/message.js @@ -0,0 +1,10 @@ +export const ERROR_MESSAGE = { + INVALID_PURCHASE_AMOUNT: '1000์›์œผ๋กœ ๋‚˜๋ˆ„์–ด ๋–จ์–ด์ง€๋Š” ์ •์ˆ˜๋ฅผ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.', + + INVALID_LOTTO_COUNT: '๋กœ๋˜ ๋ฒˆํ˜ธ๋Š” 5๊ฐœ์—ฌ์•ผ ํ•ฉ๋‹ˆ๋‹ค.', + INVALID_LOTTO_RANGE: '๋กœ๋˜ ๋ฒˆํ˜ธ๋Š” 1์—์„œ 30์‚ฌ์ด์˜ ์ˆซ์ž ์ž…๋‹ˆ๋‹ค.', + DUPLICATE_LOTTO_NUMBERS: '๋กœ๋˜ ๋ฒˆํ˜ธ๋Š” ์ค‘๋ณต๋  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.', + + INVALID_BONUS_NUMBER_RANGE: '๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ๋Š” 1์—์„œ 30์‚ฌ์ด์˜ ์ˆซ์ž ์ž…๋‹ˆ๋‹ค.', + DUPLICATE_BONUS_NUMBER: '๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ๋Š” ๋‹น์ฒจ ๋ฒˆํ˜ธ์™€ ์ค‘๋ณต๋  ์ˆ˜ ์—†์Šต๋‹ˆ๋‹ค.', +}; diff --git a/src/controller/LottoController.js b/src/controller/LottoController.js new file mode 100644 index 00000000..54ca44d6 --- /dev/null +++ b/src/controller/LottoController.js @@ -0,0 +1,91 @@ +import { InputView, OutputView } from '../view.js'; +import LottoResultCalculator from '../service/LottoResultCalculator.js'; +import { LottoCreator } from '../util/LottoCreator.js'; +import { Validation } from '../util/Validation.js'; + +export default class LottoController { + #lottos; + #winnerLotto; + #bonusNumber; + #lottoResultArray; + #lottoResult; + constructor() { + this.#lottos = []; + this.#lottoResultArray = []; + this.#lottoResult = new Map(); + } + + async runLottoMachine() { + const purchaseAmount = await this.#getValidPurchaseAmount(); + + const randomLotto = LottoCreator.setLotto(purchaseAmount); + this.#lottos = LottoCreator.LottoGenerator(randomLotto); + this.#printLotto(randomLotto); + + this.#winnerLotto = await this.#getValidWinningLotto(); + this.#bonusNumber = await this.#getValidBonusNumber(); + + this.#calculatorLotto(); + this.#printResult(this.#lottoResult); + } + + async #getValidPurchaseAmount() { + while (true) { + try { + const purchaseAmountNumber = await InputView.askAmount(); + Validation.validateLottoCount(purchaseAmountNumber); + return purchaseAmountNumber; + } catch (error) { + OutputView.printErrorMessage(error.message); + } + } + } + async #getValidWinningLotto() { + while (true) { + try { + const winningNumberString = await InputView.askWinningLotto(); + Validation.validateLottoNumber(winningNumberString); + return winningNumberString; + } catch (error) { + OutputView.printErrorMessage(error.message); + } + } + } + async #getValidBonusNumber() { + while (true) { + try { + const bonusNumberAsNumber = await InputView.askBonusNumber(); + Validation.validateBonusNumber(bonusNumberAsNumber, this.#winnerLotto); + return bonusNumberAsNumber; + } catch (error) { + OutputView.printErrorMessage(error.message); + } + } + } + #calculatorLotto() { + this.#lottos.forEach((lotto) => { + this.#lottoResultArray.push( + LottoResultCalculator.getRank( + lotto, + this.#winnerLotto, + this.#bonusNumber, + ), + ); + }); + this.#statisticsLotto(); + } + #statisticsLotto() { + for (let rank = 0; rank <= 6; rank++) { + this.#lottoResult.set(rank, 0); + } + this.#lottoResultArray.forEach((rank) => { + this.#lottoResult.set(rank, this.#lottoResult.get(rank) + 1); + }); + } + #printResult(lottoResult) { + OutputView.printResult(lottoResult); + } + #printLotto(randomLottos) { + OutputView.printPurchasedLottos(randomLottos); + } +} diff --git a/src/model/Lotto.js b/src/model/Lotto.js new file mode 100644 index 00000000..5e8f2856 --- /dev/null +++ b/src/model/Lotto.js @@ -0,0 +1,56 @@ +import { LOTTO_CONSTANTS } from '../constants/format.js'; +import { ERROR_MESSAGE } from '../constants/message.js'; + +class Lotto { + #numbers; + + constructor(numbers) { + this.#validate(numbers); + this.#numbers = numbers; + } + + #validate(numbers) { + this.#validateLength(numbers); + this.#validateRange(numbers); + this.#validateNoDuplicates(numbers); + } + + #validateLength(numbers) { + if (numbers.length !== LOTTO_CONSTANTS.NUMBER_COUNT) { + throw new Error(ERROR_MESSAGE.INVALID_LOTTO_COUNT); + } + } + #validateRange(numbers) { + const isValid = (number) => + Number.isInteger(number) && + number >= LOTTO_CONSTANTS.MIN_NUMBER && + number <= LOTTO_CONSTANTS.MAX_NUMBER; + + if (numbers.some((number) => !isValid(number))) + throw new Error(ERROR_MESSAGE.INVALID_LOTTO_RANGE); + } + + #validateNoDuplicates(numbers) { + const uniqueNumbers = new Set(numbers); + if (uniqueNumbers.size !== numbers.length) + throw new Error(ERROR_MESSAGE.DUPLICATE_LOTTO_NUMBERS); + } + // getNumber() { + // /* + // return this.#numbers ๋กœ ๋ฐ˜ํ™˜ํ•  ๊ฒฝ์šฐ ์›๋ณธ ๋ฉ”๋ชจ๋ฆฌ์ฃผ์†Œ๋ฅผ ๊ทธ๋Œ€๋กœ ๋ฐ˜ํ™˜ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์บก์Аํ™”๊ฐ€ ๊นจ์งˆ ์ˆ˜ ์žˆ๋‹ค. + // ๋”ฐ๋ผ์„œ ์Šคํ”„๋ ˆ๋“œ ์—ฐ์‚ฐ์ž[...array] ๋ฅผ ์‚ฌ์šฉํ•˜์—ฌ ๋ณต์‚ฌ๋ณธ์„ ์•ˆ์ „ํ•˜๊ฒŒ return ํ•  ์ˆ˜ ์žˆ๋‹ค. + // */ + // return [...this.#numbers]; + // } + matchCount(winningNumber) { + const matchNumber = this.#numbers.filter((number) => + winningNumber.includes(number), + ).length; + return matchNumber; + } + containBonusNumber(bonusNumber) { + return this.#numbers.includes(bonusNumber); + } +} + +export default Lotto; diff --git a/src/service/LottoResultCalculator.js b/src/service/LottoResultCalculator.js new file mode 100644 index 00000000..062f6950 --- /dev/null +++ b/src/service/LottoResultCalculator.js @@ -0,0 +1,16 @@ +export default class LottoResultCalculator { + static getRank(lottoNumbers, winningNumber, bonusNumber) { + return this.getTotalRank( + lottoNumbers.matchCount(winningNumber), + lottoNumbers.containBonusNumber(bonusNumber), + ); + } + static getTotalRank(matchCount, hasBonus) { + if (matchCount === 5) return 1; + if (matchCount === 4 && hasBonus) return 2; + if (matchCount === 4) return 3; + if (matchCount === 3) return 4; + if (matchCount === 2) return 5; + return 0; + } +} diff --git a/src/service/randomNumbers.js b/src/service/randomNumbers.js new file mode 100644 index 00000000..56f23d25 --- /dev/null +++ b/src/service/randomNumbers.js @@ -0,0 +1,11 @@ +import { LOTTO_CONSTANTS } from '../constants/format.js'; +import { MissionUtils } from '@woowacourse/mission-utils'; +const randomNumbers = () => { + const lottoNumbers = MissionUtils.Random.pickUniqueNumbersInRange( + LOTTO_CONSTANTS.MIN_NUMBER, + LOTTO_CONSTANTS.MAX_NUMBER, + LOTTO_CONSTANTS.NUMBER_COUNT, + ); + return lottoNumbers; +}; +export default randomNumbers; diff --git a/src/util/LottoCreator.js b/src/util/LottoCreator.js new file mode 100644 index 00000000..74a2fa72 --- /dev/null +++ b/src/util/LottoCreator.js @@ -0,0 +1,25 @@ +import randomNumbers from '../service/randomNumbers.js'; +import Lotto from '../model/Lotto.js'; +import { LOTTO_CONSTANTS } from '../constants/format.js'; + +export const LottoCreator = { + setLotto(purchaseAmount) { + const lottoCount = purchaseAmount / LOTTO_CONSTANTS.PRICE; + let lottos = []; + for (let i = 0; i < lottoCount; i += 1) { + const numbers = sortNumber(randomNumbers()); + lottos.push(numbers); + } + return lottos; + }, + LottoGenerator(randomLotto) { + let lottoObjects = []; + randomLotto.forEach((lottos) => { + lottoObjects.push(new Lotto(lottos)); + }); + return lottoObjects; + }, +}; +const sortNumber = (numbers) => { + return [...numbers].sort((a, b) => a - b); +}; diff --git a/src/util/Validation.js b/src/util/Validation.js new file mode 100644 index 00000000..43dd9848 --- /dev/null +++ b/src/util/Validation.js @@ -0,0 +1,57 @@ +import { LOTTO_CONSTANTS } from '../constants/format.js'; +import { ERROR_MESSAGE } from '../constants/message.js'; + +export const Validation = { + //๊ตฌ์ž… ๊ธˆ์•ก์„ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค + validateLottoCount(purchase) { + const isValid = + Number.isInteger(purchase) && + purchase > 0 && + purchase % LOTTO_CONSTANTS.PRICE === 0; + if (!isValid) throw new Error(ERROR_MESSAGE.INVALID_PURCHASE_AMOUNT); + }, + //๋ณด๋„ˆ์Šค ๋ฒˆํ˜ธ๋ฅผ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค + validateBonusNumber(bonusNumber, winnerLotto) { + bonusNumberValidateRange(bonusNumber); + bonusNumberValidateNoDuplicates(bonusNumber, winnerLotto); + }, + //๋กœ๋˜ ๋ฒˆํ˜ธ๋ฅผ ๊ฒ€์ฆํ•ฉ๋‹ˆ๋‹ค + validateLottoNumber(numbers) { + lottoValidateLength(numbers); + lottoValidateRange(numbers); + lottoValidateNoDuplicates(numbers); + }, +}; +const bonusNumberValidateRange = (bonusNumber) => { + const isValid = (number) => + Number.isInteger(number) && + number >= LOTTO_CONSTANTS.MIN_NUMBER && + number <= LOTTO_CONSTANTS.MAX_NUMBER; + if (!isValid(bonusNumber)) + throw new Error(ERROR_MESSAGE.INVALID_BONUS_NUMBER_RANGE); +}; +const bonusNumberValidateNoDuplicates = (bonusNumber, winnerLotto) => { + if (winnerLotto.includes(bonusNumber)) + throw new Error(ERROR_MESSAGE.DUPLICATE_BONUS_NUMBER); +}; + +const lottoValidateLength = (numbers) => { + if (numbers.length !== LOTTO_CONSTANTS.NUMBER_COUNT) { + throw new Error(ERROR_MESSAGE.INVALID_LOTTO_COUNT); + } +}; +const lottoValidateRange = (numbers) => { + const isValid = (number) => + Number.isInteger(number) && + number >= LOTTO_CONSTANTS.MIN_NUMBER && + number <= LOTTO_CONSTANTS.MAX_NUMBER; + + if (numbers.some((number) => !isValid(number))) + throw new Error(ERROR_MESSAGE.INVALID_LOTTO_RANGE); +}; + +const lottoValidateNoDuplicates = (numbers) => { + const uniqueNumbers = new Set(numbers); + if (uniqueNumbers.size !== numbers.length) + throw new Error(ERROR_MESSAGE.DUPLICATE_LOTTO_NUMBERS); +};