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 @@
+
+
+ 🎱 행운의 로또
+
+
-
-
- 🎱 행운의 로또
-
-
+
+
+
+
-
-
-
🎱 행운의 로또
-
-
-
+
+
+
+
+
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',
}),
],
};