diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000000..6682b83093 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,26 @@ +module.exports = { + env: { + browser: true, + es2021: true, + es6: true, + node: true, + }, + extends: ['tui', 'prettier'], + parserOptions: { + sourceType: 'module', + /** spread eslint error - 최신버젼의 ecmaVersion*/ + ecmaVersion: 'latest', + }, + rules: { + 'no-var': 'error', + 'max-depth': ['error', 2], + 'max-lines-per-function': ['error', 15], + 'no-console': 'warn', + 'no-param-reassign': 'error', + 'padding-line-between-statements': 0, + 'newline-before-return': 0, + 'no-undefined': 0, + 'no-constant-condition': 0, + 'no-unused-private-class-members': 0, + }, +}; diff --git a/.prettierrc.js b/.prettierrc.js new file mode 100644 index 0000000000..7c8d0ba2f0 --- /dev/null +++ b/.prettierrc.js @@ -0,0 +1,19 @@ +// NHN convention +module.exports = { + singleQuote: true, + printWidth: 100, + tabWidth: 2, + useTabs: false, + semi: true, + quoteProps: 'as-needed', + jsxSingleQuote: false, + trailingComma: 'es5', + arrowParens: 'always', + endOfLine: 'lf', + bracketSpacing: true, + jsxBracketSameLine: false, + requirePragma: false, + insertPragma: false, + proseWrap: 'preserve', + vueIndentScriptAndStyle: false, +}; diff --git a/docs/todo.md b/docs/todo.md new file mode 100644 index 0000000000..b3f3764c5c --- /dev/null +++ b/docs/todo.md @@ -0,0 +1,21 @@ +## 투두 리스트 + + + +**로또 모델** + +- [✅] 로또 모델에 숫자 배열을 인자로 넣어서 인스턴스를 생성할 수 있다. +- [✅] 로또 모델의 배열에 들어갈 값은 1이상 45이하의 숫자여야 한다. +- [✅] 로또 모델에 들어갈 배열의 길이는 6이어야 한다. +- [✅] 로또 모델의 번호와 당첨 번호를 비교하여 등수를 반환할 수 있어야 한다. + +**로또 게임 모델** + +- [✅] 로또 게임 모델에 금액이 정상적으로 입력되면, 구매할 수 있는 로또의 수를 반환할 수 있어야 한다. +- [✅] 금액은 1000이상의 숫자여야한다. +- [✅] 로또 번호 배열들을 입력하여 로또 모델을 생성하고 관리할 수 있어야 한다. diff --git a/index.html b/index.html index 6b7c4f4e23..295fc452b2 100644 --- a/index.html +++ b/index.html @@ -1,17 +1,65 @@ + + + 🎱 행운의 로또 + + - - - 🎱 행운의 로또 - - + +
+

🎱 행운의 로또

+
+

금액을 입력하는 섹션입니다.

+

구입할 금액을 입력해주세요

+
+ + +
+
- -
-

🎱 행운의 로또

-
- - +
+

구매한 로또를 확인하는 섹션입니다.

+
+ +
+
+
+ +
+
+
+

당첨 번호 입력 섹션

+

지난 주 당첨번호 6개와 보너스 번호 1개를 입력해주세요.

+
+
+
+

당첨 번호

+ + + + + + +
+ +
+

보너스 번호

+ +
+
+ + +
+
+
+ + diff --git a/package-lock.json b/package-lock.json index 0a7224a14e..3c707c6dca 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1154,6 +1154,78 @@ "integrity": "sha512-ws57AidsDvREKrZKYffXddNkyaF14iHNHm8VQnZH6t99E8gczjNN0GpvcGny0imC80yQ0tHz1xVUKk/KFQSUyA==", "dev": true }, + "@eslint/eslintrc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.1.0.tgz", + "integrity": "sha512-C1DfL7XX4nPqGd6jcP01W9pVM1HYCuUkFk1432D7F0v3JSlUIeOYn9oCoi3eoLZ+iwBSb29BMFxxny0YrrEZqg==", + "dev": true, + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.3.1", + "globals": "^13.9.0", + "ignore": "^4.0.6", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.0.4", + "strip-json-comments": "^3.1.1" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "globals": { + "version": "13.12.1", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", + "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "ignore": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", + "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", + "dev": true + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "@humanwhocodes/config-array": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.9.3.tgz", + "integrity": "sha512-3xSMlXHh03hCcCmFc0rbKp3Ivt2PFEJnQUJDDMTJQ2wkECZWdq4GePs2ctc5H8zV+cHPaq8k2vU8mrQjA6iHdQ==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true + }, "@istanbuljs/load-nyc-config": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/@istanbuljs/load-nyc-config/-/load-nyc-config-1.1.0.tgz", @@ -1953,6 +2025,12 @@ "integrity": "sha512-m7VZ3jwz4eK6A4Vtt8Ew1/mNbP24u0FhdyfA7fSvnJR6LMdfOYnmuIrrJAgrYfYJ10F/otaHTtrtrtmHdMNzEw==", "dev": true }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true + }, "acorn-walk": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/acorn-walk/-/acorn-walk-7.2.0.tgz", @@ -2996,6 +3074,15 @@ "buffer-indexof": "^1.0.0" } }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, "dom-converter": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/dom-converter/-/dom-converter-0.2.0.tgz", @@ -3081,6 +3168,12 @@ "integrity": "sha512-Hk61vXXKRb2cd3znPE9F+2pLWdIOmP7GjiTj45y6L3W/lO+hSnUSUhq+6lEaERWBdZOHbk2s3YV5c9xVl3boVw==", "dev": true }, + "email-addresses": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/email-addresses/-/email-addresses-3.1.0.tgz", + "integrity": "sha512-k0/r7GrWVL32kZlGwfPNgB2Y/mMXVTq/decgLczm/j34whdaspNrZO8CnXPf1laaHxI6ptUlsnAxN+UAPw+fzg==", + "dev": true + }, "emittery": { "version": "0.8.1", "resolved": "https://registry.npmjs.org/emittery/-/emittery-0.8.1.tgz", @@ -3173,6 +3266,166 @@ "source-map": "~0.6.1" } }, + "eslint": { + "version": "8.9.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.9.0.tgz", + "integrity": "sha512-PB09IGwv4F4b0/atrbcMFboF/giawbBLVC7fyDamk5Wtey4Jh2K+rYaBhCAbUyEI4QzB1ly09Uglc9iCtFaG2Q==", + "dev": true, + "requires": { + "@eslint/eslintrc": "^1.1.0", + "@humanwhocodes/config-array": "^0.9.2", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "functional-red-black-tree": "^1.0.1", + "glob-parent": "^6.0.1", + "globals": "^13.6.0", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.0.4", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0", + "v8-compile-cache": "^2.0.3" + }, + "dependencies": { + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.12.1", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.12.1.tgz", + "integrity": "sha512-317dFlgY2pdJZ9rspXDks7073GpDmXdfbM3vYYp0HAMKGDh1FfWPleI2ljVNLQX5M5lXcAslTcPTrOrMEFOjyw==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "requires": { + "argparse": "^2.0.1" + } + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dev": true, + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + } + } + }, + "eslint-config-prettier": { + "version": "8.4.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.4.0.tgz", + "integrity": "sha512-CFotdUcMY18nGRo5KGsnNxpznzhkopOcOo0InID+sgQssPrzjvsyKZPvOgymTFeHrFuC3Tzdf2YndhXtULK9Iw==", + "dev": true + }, + "eslint-config-tui": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/eslint-config-tui/-/eslint-config-tui-5.0.0.tgz", + "integrity": "sha512-i//g6IUvpf5h/WdYCfL5h55OQBtIlA+asy9GmhYw4YIGLV5GxPEJ3i88SGv5GB/UYJiQs5JdnCHn685+qpawjg==", + "dev": true + }, + "eslint-plugin-prettier": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-4.0.0.tgz", + "integrity": "sha512-98MqmCJ7vJodoQK359bqQWaxOE0CS8paAz/GgjaZLyex4TTk3g9HugoO89EqWCrFiOqn9EVvcoo7gZzONCWVwQ==", + "dev": true, + "requires": { + "prettier-linter-helpers": "^1.0.0" + } + }, "eslint-scope": { "version": "5.1.1", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", @@ -3191,12 +3444,55 @@ } } }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dev": true, + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "dev": true + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "dev": true + }, + "espree": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.1.tgz", + "integrity": "sha512-bvdyLmJMfwkV3NCRl5ZhJf22zBFo1y8bYh3VYb+bfzqNB4Je68P2sSuXyuFquzWLebHpNd2/d5uv7yoP9ISnGQ==", + "dev": true, + "requires": { + "acorn": "^8.7.0", + "acorn-jsx": "^5.3.1", + "eslint-visitor-keys": "^3.3.0" + } + }, "esprima": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, "esrecurse": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", @@ -3344,6 +3640,12 @@ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, + "fast-diff": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz", + "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==", + "dev": true + }, "fast-glob": { "version": "3.2.11", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.11.tgz", @@ -3402,6 +3704,32 @@ "bser": "2.1.1" } }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "filename-reserved-regex": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/filename-reserved-regex/-/filename-reserved-regex-2.0.0.tgz", + "integrity": "sha1-q/c9+rc10EVECr/qLZHzieu/oik=", + "dev": true + }, + "filenamify": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/filenamify/-/filenamify-4.3.0.tgz", + "integrity": "sha512-hcFKyUG57yWGAzu1CMt/dPzYZuv+jAJUT85bL8mrXvNe6hWj6yEHEc4EdcgiA6Z3oi1/9wXJdZPXF2dZNgwgOg==", + "dev": true, + "requires": { + "filename-reserved-regex": "^2.0.0", + "strip-outer": "^1.0.1", + "trim-repeated": "^1.0.0" + } + }, "fill-range": { "version": "7.0.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", @@ -3464,6 +3792,22 @@ "path-exists": "^4.0.0" } }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.5.tgz", + "integrity": "sha512-WIWGi2L3DyTUvUrwRKgGi9TwxQMUEqPOPQBVi71R96jZXJdFskXEmf54BoZaS1kknGODoIGASGEzBUYdyMCBJg==", + "dev": true + }, "follow-redirects": { "version": "1.14.9", "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.14.9.tgz", @@ -3493,6 +3837,17 @@ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", "dev": true }, + "fs-extra": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", + "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.0", + "jsonfile": "^4.0.0", + "universalify": "^0.1.0" + } + }, "fs-monkey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.3.tgz", @@ -3518,6 +3873,12 @@ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", "dev": true }, + "functional-red-black-tree": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz", + "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", + "dev": true + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -3553,6 +3914,29 @@ "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", "dev": true }, + "gh-pages": { + "version": "3.2.3", + "resolved": "https://registry.npmjs.org/gh-pages/-/gh-pages-3.2.3.tgz", + "integrity": "sha512-jA1PbapQ1jqzacECfjUaO9gV8uBgU6XNMV0oXLtfCX3haGLe5Atq8BxlrADhbD6/UdG9j6tZLWAkAybndOXTJg==", + "dev": true, + "requires": { + "async": "^2.6.1", + "commander": "^2.18.0", + "email-addresses": "^3.0.1", + "filenamify": "^4.3.0", + "find-cache-dir": "^3.3.1", + "fs-extra": "^8.1.0", + "globby": "^6.1.0" + }, + "dependencies": { + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + } + } + }, "glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", @@ -3844,6 +4228,24 @@ "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", "dev": true }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "dependencies": { + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true + } + } + }, "import-local": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.1.0.tgz", @@ -4716,6 +5118,12 @@ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha1-nbe1lJatPzz+8wp1FC0tkwrXJlE=", + "dev": true + }, "json5": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", @@ -4725,6 +5133,15 @@ "minimist": "^1.2.5" } }, + "jsonfile": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", + "integrity": "sha1-h3Gq4HmbZAdrdmQPygWPnBDjPss=", + "dev": true, + "requires": { + "graceful-fs": "^4.1.6" + } + }, "kind-of": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", @@ -4808,6 +5225,12 @@ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", "dev": true }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true + }, "lower-case": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", @@ -5202,6 +5625,15 @@ "tslib": "^2.0.3" } }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "requires": { + "callsites": "^3.0.0" + } + }, "parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -5416,6 +5848,21 @@ "integrity": "sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=", "dev": true }, + "prettier": { + "version": "2.5.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.5.1.tgz", + "integrity": "sha512-vBZcPRUR5MZJwoyi3ZoyQlc1rXeEck8KgeC9AwwOn+exuxLxq5toTRDTSaVrXHxelDMHy9zlicw8u66yxoSUFg==", + "dev": true + }, + "prettier-linter-helpers": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz", + "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==", + "dev": true, + "requires": { + "fast-diff": "^1.1.2" + } + }, "pretty-error": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pretty-error/-/pretty-error-4.0.0.tgz", @@ -5613,6 +6060,12 @@ "define-properties": "^1.1.3" } }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "dev": true + }, "regexpu-core": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-5.0.1.tgz", @@ -6119,6 +6572,15 @@ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true }, + "strip-outer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/strip-outer/-/strip-outer-1.0.1.tgz", + "integrity": "sha512-k55yxKHwaXnpYGsOzg4Vl8+tDrWylxDEpknGjhTiZB8dFRU5rTo9CAzeycivxV3s+zlTKwrs6WxMxR95n26kwg==", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, "style-loader": { "version": "3.3.1", "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-3.3.1.tgz", @@ -6251,6 +6713,12 @@ "minimatch": "^3.0.4" } }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha1-f17oI66AUgfACvLfSoTsP8+lcLQ=", + "dev": true + }, "throat": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/throat/-/throat-6.0.1.tgz", @@ -6310,6 +6778,15 @@ "punycode": "^2.1.1" } }, + "trim-repeated": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/trim-repeated/-/trim-repeated-1.0.0.tgz", + "integrity": "sha1-42RqLqTokTEr9+rObPsFOAvAHCE=", + "dev": true, + "requires": { + "escape-string-regexp": "^1.0.2" + } + }, "tslib": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.3.1.tgz", @@ -6429,6 +6906,12 @@ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", "dev": true }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, "v8-to-istanbul": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/v8-to-istanbul/-/v8-to-istanbul-8.1.1.tgz", diff --git a/package.json b/package.json index b57aa49db4..7367bf5e3e 100644 --- a/package.json +++ b/package.json @@ -3,10 +3,13 @@ "version": "1.0.0", "main": "index.js", "license": "MIT", + "homepage": "https://usageness.github.io/javascript-lotto", "scripts": { "test": "jest --watch --no-cache", "start": "webpack serve --open", - "build": "webpack" + "build": "webpack --mode=production", + "predeploy": "npm run build", + "deploy": "gh-pages -d dist" }, "devDependencies": { "@babel/core": "^7.16.12", @@ -14,9 +17,15 @@ "babel-jest": "^27.4.6", "babel-loader": "^8.2.3", "clean-webpack-plugin": "^4.0.0", - "html-webpack-plugin": "^5.5.0", "css-loader": "^6.6.0", + "eslint": "^8.9.0", + "eslint-config-prettier": "^8.4.0", + "eslint-config-tui": "^5.0.0", + "eslint-plugin-prettier": "^4.0.0", + "gh-pages": "^3.2.3", + "html-webpack-plugin": "^5.5.0", "jest": "^27.4.7", + "prettier": "2.5.1", "style-loader": "^3.3.1", "webpack": "^5.69.1", "webpack-cli": "^4.9.2", diff --git a/src/css/converter.css b/src/css/converter.css new file mode 100644 index 0000000000..46152b6fd1 --- /dev/null +++ b/src/css/converter.css @@ -0,0 +1,37 @@ +.converter { + position: absolute; + opacity: 0; + cursor: pointer; + height: 0; + width: 0; +} +.checkmark { + display: block; + position: relative; + margin-top: 1rem; + top: 0; + right: 0; + height: 14px; + width: 34px; + background-color: rgba(33, 33, 33, 0.08); + border-radius: 7px; +} +.converter:checked ~ .checkmark { + background-color: #80deea; +} + +.checkmark .circle { + width: 20px; + height: 20px; + border-radius: 100%; + position: absolute; + top: -3px; + transition: 0.2s transform; +} +.converter:checked ~ .checkmark .circle { + background-color: #00bcd4; + transform: translateX(70%); +} +.converter ~ .checkmark .circle { + background-color: #ededed; +} diff --git a/src/css/index.css b/src/css/index.css index e69de29bb2..daca1a1306 100644 --- a/src/css/index.css +++ b/src/css/index.css @@ -0,0 +1,132 @@ +input[type='number']::-webkit-outer-spin-button, +input[type='number']::-webkit-inner-spin-button { + -webkit-appearance: none; + margin: 0; +} + +input { + outline: none; +} + +body { + width: 98vw; + height: 90vh; + margin: auto; + background-color: rgba(0, 0, 0, 0.07); + font-family: 'NanumBarunGothic', sans-serif; + + display: flex; + justify-content: center; + align-items: center; +} + +#app { + width: 25vw; + min-width: 414px; + padding: 50px; + + background-color: #ffffff; + border: 1px solid rgba(0, 0, 0, 0.12); +} + +.header-title { + font-size: 34px; + text-align: center; +} + +#charge-input-form { + display: flex; + height: 36px; +} + +#charge-input { + flex: 1; + margin-right: 20px; + border: 1px solid #b4b4b4; + border-radius: 4px; +} + +button { + border-radius: 4px; + background-color: #00bcd4; + color: #ffffff; + border: 0; + min-height: 36px; +} + +section { + margin-bottom: 20px; +} + +#lotto-section { + display: flex; + flex-direction: row; +} + +.lotto-wrapper { + flex: 1; +} + +.lotto { + font-size: 34px; +} + +.lotto .number { + font-size: 16px; +} + +#lotto-container { + display: flex; +} +#lotto-container[data-visible-state='false'] { + flex-direction: row; + gap: 10px; +} + +#lotto-container[data-visible-state='false'] .number { + display: none; +} + +#lotto-container[data-visible-state='true'] { + flex-direction: column; +} + +#lotto-container[data-visible-state='true'] .lotto { + display: flex; + align-items: center; + gap: 10px; +} + +.win-number-input-wrapper { + display: flex; + gap: 10px; + justify-content: space-between; +} + +#result-button { + width: 100%; + margin-top: 10px; +} + +.win-number-input-wrapper input { + width: 30px; + height: 30px; +} + +.bonus-number-wrapper { + display: flex; + align-items: flex-end; + flex-direction: column; +} + +#align-converter-container { + display: flex; + flex-direction: column; + align-items: flex-end; +} + +label[for='align-converter'] { + display: flex; + flex-direction: column; + align-items: flex-end; +} diff --git a/src/css/nanumbarungothic.css b/src/css/nanumbarungothic.css new file mode 100644 index 0000000000..4807967135 --- /dev/null +++ b/src/css/nanumbarungothic.css @@ -0,0 +1,42 @@ +@font-face { + font-family: "NanumBarunGothic"; + font-style: normal; + font-weight: 400; + src: url("//cdn.jsdelivr.net/font-nanumlight/1.0/NanumBarunGothicWeb.eot"); + src: url("//cdn.jsdelivr.net/font-nanumlight/1.0/NanumBarunGothicWeb.eot?#iefix") + format("embedded-opentype"), + url("//cdn.jsdelivr.net/font-nanumlight/1.0/NanumBarunGothicWeb.woff") + format("woff"), + url("//cdn.jsdelivr.net/font-nanumlight/1.0/NanumBarunGothicWeb.ttf") + format("truetype"); +} + +@font-face { + font-family: "NanumBarunGothic"; + font-style: normal; + font-weight: 700; + src: url("//cdn.jsdelivr.net/font-nanumlight/1.0/NanumBarunGothicWebBold.eot"); + src: url("//cdn.jsdelivr.net/font-nanumlight/1.0/NanumBarunGothicWebBold.eot?#iefix") + format("embedded-opentype"), + url("//cdn.jsdelivr.net/font-nanumlight/1.0/NanumBarunGothicWebBold.woff") + format("woff"), + url("//cdn.jsdelivr.net/font-nanumlight/1.0/NanumBarunGothicWebBold.ttf") + format("truetype"); +} + +@font-face { + font-family: "NanumBarunGothic"; + font-style: normal; + font-weight: 300; + src: url("//cdn.jsdelivr.net/font-nanumlight/1.0/NanumBarunGothicWebLight.eot"); + src: url("//cdn.jsdelivr.net/font-nanumlight/1.0/NanumBarunGothicWebLight.eot?#iefix") + format("embedded-opentype"), + url("//cdn.jsdelivr.net/font-nanumlight/1.0/NanumBarunGothicWebLight.woff") + format("woff"), + url("//cdn.jsdelivr.net/font-nanumlight/1.0/NanumBarunGothicWebLight.ttf") + format("truetype"); +} + +.nanumbarungothic * { + font-family: "NanumBarunGothic", sans-serif; +} diff --git a/src/index.js b/src/index.js index 241677a685..fddec6c039 100644 --- a/src/index.js +++ b/src/index.js @@ -1,2 +1,7 @@ -import "./css/index"; -import "./js/app"; +import './css/index.css'; +import './css/converter.css'; +import './css/nanumbarungothic.css'; +import './js/utils/customPrototypeMethod'; +import RacingGameManager from './js/app'; + +export default new RacingGameManager().init(); diff --git a/src/js/__tests__/app.test.js b/src/js/__tests__/app.test.js deleted file mode 100644 index 43e6af61f7..0000000000 --- a/src/js/__tests__/app.test.js +++ /dev/null @@ -1,5 +0,0 @@ -describe("테스트 그룹 ", () => { - it("테스트 명세", () => { - expect(true).toBe(true); - }); -}); diff --git a/src/js/__tests__/lotto.test.js b/src/js/__tests__/lotto.test.js new file mode 100644 index 0000000000..3e74f9abd7 --- /dev/null +++ b/src/js/__tests__/lotto.test.js @@ -0,0 +1,18 @@ +import { ERROR_MESSAGE } from '../constants/errorMessage'; +import Lotto from '../models/Lotto'; + +describe('로또 모델 테스트', () => { + it('로또 모델에 숫자 배열을 인자로 넣어서 인스턴스를 생성할 수 있다.', () => { + const lotto = Lotto.create([1, 2, 3, 4, 5, 6]); + expect(lotto.lottoNumbers).toContain(1, 2, 3, 4, 5, 6); + }); + + it('로또 모델의 배열에 들어갈 값은 1이상 45이하의 숫자여야 한다.', () => { + const invalidInput = [1, 2, 3, 4, 5, 46]; + try { + Lotto.create(invalidInput); + } catch ({ message }) { + expect(message).toEqual(ERROR_MESSAGE.LOTTO_NUMBER_IS_INVALIDATE); + } + }); +}); diff --git a/src/js/__tests__/lottoGame.test.js b/src/js/__tests__/lottoGame.test.js new file mode 100644 index 0000000000..52ed9d55c9 --- /dev/null +++ b/src/js/__tests__/lottoGame.test.js @@ -0,0 +1,44 @@ +import '../utils/customPrototypeMethod'; +import { ERROR_MESSAGE } from '../constants/errorMessage'; +import LottoGame from '../models/LottoGame'; + +describe('로또 게임 모델 테스트', () => { + it('로또 게임 모델에 금액이 정상적으로 입력되면, 구매할 수 있는 로또의 수를 반환할 수 있어야 한다.', () => { + const lottoGame = new LottoGame(); + const charge = 5000; + const expectedAvailableLottoAmount = 5; + const availableLottoAmount = lottoGame.exchangeChargeToLottoAmount(charge); + expect(availableLottoAmount).toBe(expectedAvailableLottoAmount); + }); + + it('금액은 1000이상의 숫자여야한다.', () => { + const lottoGame = new LottoGame(); + const lessThanLottoPriceCharge = 500; + try { + lottoGame.exchangeChargeToLottoAmount(lessThanLottoPriceCharge); + } catch ({ message }) { + expect(message).toEqual(ERROR_MESSAGE.CHARGE_IS_INVALIDATE); + } + }); + + it('로또 번호 배열들을 입력하여 로또 모델을 생성하고 관리할 수 있어야 한다.', () => { + const lottoGame = new LottoGame(); + const charge = 5000; + const availableLottoAmount = lottoGame.exchangeChargeToLottoAmount(charge); + + lottoGame.createLottoList(charge); + + expect(lottoGame.lottoList.length).toBe(availableLottoAmount); + }); + + /** 이 부분이 lottoGame의 테스트인지, 유틸 함수에 대한 테스트인지 궁금하다. */ + it('lottoList의 getter는 깊게 복사된 값을 반환한다.', () => { + const lottoGame = new LottoGame(); + const charge = 5000; + + lottoGame.createLottoList(charge); + + const lottoListFromGetterFunc = lottoGame.getLottoList(); + expect(lottoListFromGetterFunc).toEqual(lottoGame.lottoList); + }); +}); diff --git a/src/js/app.js b/src/js/app.js index e69de29bb2..b7e254c30c 100644 --- a/src/js/app.js +++ b/src/js/app.js @@ -0,0 +1,43 @@ +import LottoGameModel from './models/LottoGame'; +import { SELECTOR } from './constants/selector'; +import LottoGameView from './views'; +import { findElement } from './utils/elementSelector'; + +class LottoGameManager { + init() { + this.lottoGameModel = new LottoGameModel(); + this.lottoGameView = new LottoGameView(); + + this.$chargeForm = findElement(SELECTOR.CHARGE_INPUT_FORM); + this.$chargeInput = findElement(SELECTOR.CHARGE_INPUT); + this.$alignConverter = findElement(SELECTOR.ALIGN_CONVERTER); + + this.$chargeForm.addEventListener('submit', this.onSubmitChargeInputForm); + this.$alignConverter.addEventListener('change', this.onChangeAlignState); + } + + onSubmitChargeInputForm = (e) => { + e.preventDefault(); + try { + const { value: chargeInputStr } = this.$chargeInput; + const chargeInput = Number(chargeInputStr); + this.triggerChargeInputAction(chargeInput); + } catch ({ message }) { + alert(message); + } + }; + + triggerChargeInputAction(chargeInput) { + // mutate model + this.lottoGameModel.createLottoList(chargeInput); + // mutate view by new model state + const lottoList = this.lottoGameModel.getLottoList(); + this.lottoGameView.renderLottoSection(lottoList); + } + + onChangeAlignState = (e) => { + const { checked: alignState } = e.target; + this.lottoGameView.renderAlignState(alignState); + }; +} +export default LottoGameManager; diff --git a/src/js/constants/errorMessage.js b/src/js/constants/errorMessage.js new file mode 100644 index 0000000000..f12977bb92 --- /dev/null +++ b/src/js/constants/errorMessage.js @@ -0,0 +1,4 @@ +export const ERROR_MESSAGE = { + CHARGE_IS_INVALIDATE: '금액은 1000원 이상이어야합니다. 금액을 다시 입력해주세요.', + LOTTO_NUMBER_IS_INVALIDATE: '로또 숫자들의 값이 부정확합니다. 금액을 다시 입력해주세요.', +}; diff --git a/src/js/constants/number.js b/src/js/constants/number.js new file mode 100644 index 0000000000..eb3fde7d7d --- /dev/null +++ b/src/js/constants/number.js @@ -0,0 +1,6 @@ +export const NUMBER = { + LOTTO_NUMBER_AMOUNT: 6, + LOTTO_MIN_NUMBER: 1, + LOTTO_MAX_NUMBER: 45, + LOTTO_PRICE: 1000, +}; diff --git a/src/js/constants/selector.js b/src/js/constants/selector.js new file mode 100644 index 0000000000..e12365e7aa --- /dev/null +++ b/src/js/constants/selector.js @@ -0,0 +1,8 @@ +export const SELECTOR = { + CHARGE_INPUT_FORM: '#charge-input-form', + CHARGE_INPUT: '#charge-input', + + ALIGN_CONVERTER: '#align-converter', + PURCHASED_MESSAGE: '#purchased-message', + LOTTO_CONTAINER: '#lotto-container', +}; diff --git a/src/js/models/Lotto.js b/src/js/models/Lotto.js new file mode 100644 index 0000000000..df43855bfa --- /dev/null +++ b/src/js/models/Lotto.js @@ -0,0 +1,17 @@ +import { isValidNumber, isValidLength } from '../utils/validator'; +import { ERROR_MESSAGE } from '../constants/errorMessage'; + +class Lotto { + constructor(lottoNumbers) { + this.lottoNumbers = lottoNumbers; + } + + static create(lottoNumbers) { + if (isValidNumber(lottoNumbers) && isValidLength(lottoNumbers)) { + return new Lotto(lottoNumbers); + } + throw new Error(ERROR_MESSAGE.LOTTO_NUMBER_IS_INVALIDATE); + } +} + +export default Lotto; diff --git a/src/js/models/LottoGame.js b/src/js/models/LottoGame.js new file mode 100644 index 0000000000..e0c18005a1 --- /dev/null +++ b/src/js/models/LottoGame.js @@ -0,0 +1,48 @@ +import Lotto from './Lotto'; +import { isValidCharge, getRandomNumber } from '../utils/validator'; +import { ERROR_MESSAGE } from '../constants/errorMessage'; +import { NUMBER } from '../constants/number'; + +class LottoGameModel { + constructor() { + this.lottoList = []; + } + + getLottoList() { + /** getter로 가져간 lottoList를 변경하여도 lottoList의 멤버에겐 영향이 없다. */ + return this.lottoList.deepCopy(); + } + + createLottoList(chargeInput) { + /** 정상적이지 않은 로또가 하나라도 존재한다면, 멤버는 빈 값이고 사용자는 금액을 다시 입력하여야 한다. */ + try { + const availableLottoAmount = this.exchangeChargeToLottoAmount(chargeInput); + const newLottoList = new Array(availableLottoAmount).fill().map(() => { + const lottoNumbers = this.createLottoNumbers(); + return Lotto.create(lottoNumbers); + }); + this.lottoList = newLottoList; + } catch ({ message }) { + alert(message); + } + } + + createLottoNumbers() { + const lottoArray = new Set(); + + while (lottoArray.size < NUMBER.LOTTO_NUMBER_AMOUNT) { + lottoArray.add(getRandomNumber(lottoArray)); + } + + return [...lottoArray]; + } + + exchangeChargeToLottoAmount(charge) { + if (isValidCharge(charge)) { + return Math.floor(charge / NUMBER.LOTTO_PRICE); + } + throw new Error(ERROR_MESSAGE.CHARGE_IS_INVALIDATE); + } +} + +export default LottoGameModel; diff --git a/src/js/utils/customPrototypeMethod.js b/src/js/utils/customPrototypeMethod.js new file mode 100644 index 0000000000..9e983f42ac --- /dev/null +++ b/src/js/utils/customPrototypeMethod.js @@ -0,0 +1,4 @@ +/* eslint no-extend-native:0 */ +Array.prototype.deepCopy = function () { + return JSON.parse(JSON.stringify(this)); +}; diff --git a/src/js/utils/elementSelector.js b/src/js/utils/elementSelector.js new file mode 100644 index 0000000000..18a6719220 --- /dev/null +++ b/src/js/utils/elementSelector.js @@ -0,0 +1,2 @@ +/** findElement 함수를 Object 하위의 프로토타입으로 설정하는 것은 어떠한지 궁금합니다 */ +export const findElement = (selector) => document.querySelector(selector); diff --git a/src/js/utils/validator.js b/src/js/utils/validator.js new file mode 100644 index 0000000000..60038cbd19 --- /dev/null +++ b/src/js/utils/validator.js @@ -0,0 +1,29 @@ +import { NUMBER } from '../constants/number'; + +export function isValidLength(lottoNumber) { + return lottoNumber.length === NUMBER.LOTTO_NUMBER_AMOUNT; +} + +export function isValidNumber(lottoNumbers) { + return !lottoNumbers.some( + (number) => + !Number.isInteger(number) || + number < NUMBER.LOTTO_MIN_NUMBER || + number > NUMBER.LOTTO_MAX_NUMBER + ); +} + +export function isValidCharge(charge) { + return Number.isInteger(charge) && charge >= NUMBER.LOTTO_PRICE; +} + +export function getRandomNumber(array) { + let randomNumber = Math.floor(Math.random() * 45) + 1; + + while (array.has(randomNumber)) { + if (randomNumber >= 45) randomNumber = 1; + else randomNumber += 1; + } + + return randomNumber; +} diff --git a/src/js/views/index.js b/src/js/views/index.js new file mode 100644 index 0000000000..7591335071 --- /dev/null +++ b/src/js/views/index.js @@ -0,0 +1,28 @@ +import { SELECTOR } from '../constants/selector'; +import { findElement } from '../utils/elementSelector'; + +class LottoGameView { + constructor() { + this.$purchasedMessage = findElement(SELECTOR.PURCHASED_MESSAGE); + this.$lottoContainer = findElement(SELECTOR.LOTTO_CONTAINER); + } + + renderLottoSection(lottoList) { + this.$purchasedMessage.innerText = `총 ${lottoList.length}개를 구매하였습니다.`; + this.$lottoContainer.innerHTML = lottoList + .map((lotto) => this.generateLottoTemplate(lotto)) + .join(''); + } + + generateLottoTemplate({ lottoNumbers }) { + return `
+ 🎟️ + ${lottoNumbers.join(', ')} +
`; + } + + renderAlignState(visibleState) { + this.$lottoContainer.setAttribute('data-visible-state', visibleState); + } +} +export default LottoGameView; diff --git a/webpack.config.js b/webpack.config.js index 9a704e56c7..0fe7e7cb9b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -1,20 +1,20 @@ -const path = require("path"); -const HtmlWebpackPlugin = require("html-webpack-plugin"); -const { CleanWebpackPlugin } = require("clean-webpack-plugin"); +const path = require('path'); +const HtmlWebpackPlugin = require('html-webpack-plugin'); +const { CleanWebpackPlugin } = require('clean-webpack-plugin'); module.exports = { - mode: "development", - entry: "./src/index.js", + mode: 'development', + entry: './src/index.js', resolve: { - extensions: [".js", ".css"], + extensions: ['.js', '.css'], }, devServer: { port: 9000, }, - devtool: "source-map", + devtool: 'source-map', output: { - filename: "bundle.js", - path: path.resolve(__dirname, "dist"), + filename: 'bundle.js', + path: path.resolve(__dirname, 'dist'), }, module: { rules: [ @@ -23,23 +23,23 @@ module.exports = { exclude: /node_modules/, use: [ { - loader: "babel-loader", + loader: 'babel-loader', options: { - presets: ["@babel/preset-env"], + presets: ['@babel/preset-env'], }, }, ], }, { test: /\.css$/, - use: ["style-loader", "css-loader"], + use: ['style-loader', 'css-loader'], }, ], }, plugins: [ new CleanWebpackPlugin(), new HtmlWebpackPlugin({ - template: "./index.html", + template: './index.html', }), ], };