From f9b56755d13b2f50b9a5fc0ab2e30f6386cbaf8c Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Wed, 27 May 2026 20:39:14 +0900 Subject: [PATCH 01/32] =?UTF-8?q?feat:=20=EB=9E=9C=EB=8D=A4=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=A0=95=EB=8B=B5=EA=B0=92=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils.js | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/utils.js diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 0000000..764677a --- /dev/null +++ b/src/utils.js @@ -0,0 +1,12 @@ +export function getRandomNumbers() { + const randomNumArr = []; + + while (randomNumArr.length < 3) { + const randomNum = Random.pickNumberInRange(1, 9); + if (randomNumArr.includes(randomNum) === false) { + randomNumArr.push(randomNum); + } + } + + return randomNumArr; +} From 9bc9553af5e09800f1bc45a713be654acac2f8e1 Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Wed, 27 May 2026 20:58:41 +0900 Subject: [PATCH 02/32] =?UTF-8?q?feat:=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20?= =?UTF-8?q?=EA=B2=80=EC=82=AC=20=EB=A1=9C=EC=A7=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/utils.js | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/src/utils.js b/src/utils.js index 764677a..c25ac9a 100644 --- a/src/utils.js +++ b/src/utils.js @@ -10,3 +10,24 @@ export function getRandomNumbers() { return randomNumArr; } + +export function isVaild(userInputNumbers) { + // 1. 길이 검사 + if (userInputNumbers.length !== 3) { + return false; + } + + // 2. 중복 검사 + if (new Set(userInputNumbers).size !== 3) { + return false; + } + + // 3. 숫자 범위 검사 + for (let i = 0; i < userInputNumbers.length; i++) { + if (userInputNumbers[i] > 0 && userInputNumbers[i] < 10) { + return false; + } + } + + return true; +} From aa6507c0e62f99eb831849b2862c18a5672f9ede Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Thu, 28 May 2026 11:03:23 +0900 Subject: [PATCH 03/32] =?UTF-8?q?refactor:=20=EC=9C=A0=ED=8B=B8=20?= =?UTF-8?q?=ED=95=A8=EC=88=98=EB=93=A4=EC=9D=84=20BaseballGame=20=EA=B0=9D?= =?UTF-8?q?=EC=B2=B4=EC=9D=98=20=EB=A9=94=EC=84=9C=EB=93=9C=EB=A1=9C=20?= =?UTF-8?q?=ED=86=B5=ED=95=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BaseballGame.js | 58 +++++++++++++++++++++++++++++++++++++++++++++ src/utils.js | 33 -------------------------- 2 files changed, 58 insertions(+), 33 deletions(-) create mode 100644 src/BaseballGame.js delete mode 100644 src/utils.js diff --git a/src/BaseballGame.js b/src/BaseballGame.js new file mode 100644 index 0000000..e032b6e --- /dev/null +++ b/src/BaseballGame.js @@ -0,0 +1,58 @@ +export default function BaseballGame() { + // 1. 스트라이크, 볼, 낫싱 판별 + this.play = function (computerInputNumbers, userInputNumbers) { + let strikeCnt = 0; + let ballCnt = 0; + + for (let i = 0; i < 3; i++) { + if (userInputNumbers[i] === computerInputNumbers[i]) { + strikeCnt++; + } else if (computerInputNumbers.includes(userInputNumbers[i])) { + ballCnt++; + } + } + + const returnString = []; + if (ballCnt > 0) returnString.push(`${ballCnt}볼`); + if (strikeCnt > 0) returnString.push(`${strikeCnt}스트라이크`); + if (returnString.length === 0) { + return "낫싱"; + } else { + return returnString.join(" "); + } + }; + + // 2. 정답값 생성 (랜덤) + this.getResultNum = function () { + const resultNumArr = []; + + while (resultNumArr.length < 3) { + const randomNum = Random.pickNumberInRange(1, 9); + if (resultNumArr.includes(randomNum) === false) { + resultNumArr.push(randomNum); + } + } + return resultNumArr; + }; + + // 3. 유효값 판별 + this.isVaild = function (userInputNumbers) { + // 1) 길이 검사 + if (userInputNumbers.length !== 3) { + return false; + } + + // 2) 중복 검사 + if (new Set(userInputNumbers).size !== 3) { + return false; + } + + // 3) 숫자 범위 검사 + for (let i = 0; i < userInputNumbers.length; i++) { + if (userInputNumbers[i] > 0 && userInputNumbers[i] < 10) { + return true; + } + } + return true; + }; +} diff --git a/src/utils.js b/src/utils.js deleted file mode 100644 index c25ac9a..0000000 --- a/src/utils.js +++ /dev/null @@ -1,33 +0,0 @@ -export function getRandomNumbers() { - const randomNumArr = []; - - while (randomNumArr.length < 3) { - const randomNum = Random.pickNumberInRange(1, 9); - if (randomNumArr.includes(randomNum) === false) { - randomNumArr.push(randomNum); - } - } - - return randomNumArr; -} - -export function isVaild(userInputNumbers) { - // 1. 길이 검사 - if (userInputNumbers.length !== 3) { - return false; - } - - // 2. 중복 검사 - if (new Set(userInputNumbers).size !== 3) { - return false; - } - - // 3. 숫자 범위 검사 - for (let i = 0; i < userInputNumbers.length; i++) { - if (userInputNumbers[i] > 0 && userInputNumbers[i] < 10) { - return false; - } - } - - return true; -} From 6be6a2d008be20f8745f1ff52eb18d8a063887b0 Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Thu, 28 May 2026 14:11:34 +0900 Subject: [PATCH 04/32] =?UTF-8?q?feat:=20=EB=B2=84=ED=8A=BC=20=EC=9A=94?= =?UTF-8?q?=EC=86=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 52 +++++++++++++++++++++++++--------------------------- 1 file changed, 25 insertions(+), 27 deletions(-) diff --git a/index.html b/index.html index c85d50b..9e258dc 100644 --- a/index.html +++ b/index.html @@ -1,30 +1,28 @@ - + + + + 숫자 야구 게임 + - - - 숫자 야구 게임 - - - -
-

⚾ 숫자 야구 게임

-

- 1~9까지의 수를 중복없이 - 3개 입력해주세요.
- 올바른 예) 139
- 틀린 예) 122 -

-
- - -
-

📄 결과

-
1볼 1스트라이크
- -
- - - - + +
+

⚾ 숫자 야구 게임

+

+ 1~9까지의 수를 중복없이 + 3개 입력해주세요.
+ 올바른 예) 139
+ 틀린 예) 122 +

+
+ + +
+

📄 결과

+
+ +
+ + + From 012e2bca0edd96554f2e56f4527de53d7f23f0e9 Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Thu, 28 May 2026 14:13:50 +0900 Subject: [PATCH 05/32] =?UTF-8?q?feat:=20index.js=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.js | 66 ++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 66 insertions(+) create mode 100644 src/index.js diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..4938c90 --- /dev/null +++ b/src/index.js @@ -0,0 +1,66 @@ +// 게임 객체 import +import BaseballGame from "./BaseballGame"; + +// 이벤트가 필요한 HTML 요소(DOM)들을 미리 정의 +// 입력창, 확인 버튼, 결과를 보여줄 공간 +const userInputNum = document.querySelector(#user-input); +const submitButton = document.querySelector(#submit); +const resultDiv = document.querySelector(#result); +const reStartButton = document.querySelector(#game-restart-button); + +// 게임 객체 생성 +const game = new BaseballGame(); + +// 정답 데이터 저장할 공간 생성 +let computerNumbers = []; + +// 초기화 함수 +function initGame() { + // 1. 정답 교체 + computerNumbers = game.getResultNum(); + // 2. 입력창에 적힌 숫자 지우기 + submitButton.value=''; + // 3. 결과창 텍스트 비우기 + resultDiv.innerHTML=''; + // 4. 재시작 버튼 숨기기 + reStartButton.hidden = true; +} + +// 처음 시작 할 때 한 번 실행 +initGame(); + +// 재시작 버튼 클릭 이벤트 리스너 +reStartButton.addEventListener("click", ()=> { + // 이미 확인 버튼 이벤트 리스너에서 정답일 경우 반환 결과를 처리했음 + // 그러니까 여기서는 그냥 초기화 함수만 실행해주면 됨 + initGame(); +}) + +// 확인 버튼 클릭 이벤트 리스너 +submitButton.addEventListener("click", (event) => { + event.preventDefault(); // 폼 제출 시 새로고침 막기 + + // 1) 유저가 입력한 글자 가져오기 + userInputValue = userInputNum.value; + + // 2) game.isVaild()로 중복없는 1~9 세자리 숫자인지 검사 + // 3) 검사 통과 못하면 alert()로 경고창 띄우고 다시 입력 + if (game.isVaild(userInputValue) === false){ + alert("1~9까지의 수를 중복없이 3개 입력해주세요."); + userInputNum.value=''; // 입력창 비우기 + userInputNum.focus(); // 커서 깜빡임 + return; + } + // 4) 통과하면 game.play(정답값, 입력값) 실행 + // 배열로 처리하기로 했으니까 사용자 입력값도 문자열에서 배열로 변환 필요 + const resultString = game.play(computerNumbers,userInputValue.split("")); + + // 5) 반환된 결과를 출력 + // 정답일 경우 / 아닌 경우 분기 + if (resultString === "3스트라이크") { + resultDiv.innerHTML = "

정답!

" + reStartButton.hidden = false; + } else { + resultDiv.innerHTML = resultString; + } +}); \ No newline at end of file From fc0ed91276d9ec7cfd142ea61dca7e11c25bcff2 Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Thu, 28 May 2026 14:17:57 +0900 Subject: [PATCH 06/32] =?UTF-8?q?refactor:=20userInputValue=20=EB=B0=B0?= =?UTF-8?q?=EC=97=B4=EB=A1=9C=20=EB=B0=9B=EC=95=84=EC=84=9C=20=EB=84=A3?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index 4938c90..f79b918 100644 --- a/src/index.js +++ b/src/index.js @@ -41,7 +41,7 @@ submitButton.addEventListener("click", (event) => { event.preventDefault(); // 폼 제출 시 새로고침 막기 // 1) 유저가 입력한 글자 가져오기 - userInputValue = userInputNum.value; + userInputValue = userInputNum.value.split(''); // 2) game.isVaild()로 중복없는 1~9 세자리 숫자인지 검사 // 3) 검사 통과 못하면 alert()로 경고창 띄우고 다시 입력 @@ -53,7 +53,7 @@ submitButton.addEventListener("click", (event) => { } // 4) 통과하면 game.play(정답값, 입력값) 실행 // 배열로 처리하기로 했으니까 사용자 입력값도 문자열에서 배열로 변환 필요 - const resultString = game.play(computerNumbers,userInputValue.split("")); + const resultString = game.play(computerNumbers,userInputValue); // 5) 반환된 결과를 출력 // 정답일 경우 / 아닌 경우 분기 From 3ac145b90360cd6fac393f2162706c975ea1d54b Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Thu, 28 May 2026 14:25:11 +0900 Subject: [PATCH 07/32] =?UTF-8?q?refactor:=20userInputValue=20=EB=B0=B0?= =?UTF-8?q?=EC=97=B4=20=ED=83=80=EC=9E=85=20=EB=B3=80=ED=99=98=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80=20(=EC=97=90=EB=9F=AC=20=EB=B0=A9=EC=A7=80)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.js | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/src/index.js b/src/index.js index f79b918..75cef88 100644 --- a/src/index.js +++ b/src/index.js @@ -41,7 +41,8 @@ submitButton.addEventListener("click", (event) => { event.preventDefault(); // 폼 제출 시 새로고침 막기 // 1) 유저가 입력한 글자 가져오기 - userInputValue = userInputNum.value.split(''); + // split으로 문자열 배열로 바꿔주고, map(Number)로 숫자 배열로 변환 + userInputValue = userInputNum.value.split('').map(Number); // 2) game.isVaild()로 중복없는 1~9 세자리 숫자인지 검사 // 3) 검사 통과 못하면 alert()로 경고창 띄우고 다시 입력 @@ -58,7 +59,10 @@ submitButton.addEventListener("click", (event) => { // 5) 반환된 결과를 출력 // 정답일 경우 / 아닌 경우 분기 if (resultString === "3스트라이크") { - resultDiv.innerHTML = "

정답!

" + resultDiv.innerHTML = ` +

정답!

+

게임을 새로 시작하시겠어요?

+ `; reStartButton.hidden = false; } else { resultDiv.innerHTML = resultString; From 89bbd10baa4cc8bf90fff4e5ca4f5f48f204cece Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Thu, 28 May 2026 14:45:32 +0900 Subject: [PATCH 08/32] =?UTF-8?q?refactor:=20Random=20=EB=9D=BC=EC=9D=B4?= =?UTF-8?q?=EB=B8=8C=EB=9F=AC=EB=A6=AC=20=ED=98=B8=EC=B6=9C=20=EB=B0=A9?= =?UTF-8?q?=EB=B2=95=20=EC=98=A4=EB=A5=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BaseballGame.js | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/src/BaseballGame.js b/src/BaseballGame.js index e032b6e..3b97ac8 100644 --- a/src/BaseballGame.js +++ b/src/BaseballGame.js @@ -27,7 +27,7 @@ export default function BaseballGame() { const resultNumArr = []; while (resultNumArr.length < 3) { - const randomNum = Random.pickNumberInRange(1, 9); + const randomNum = MissionUtils.Random.pickNumberInRange(1, 9); if (resultNumArr.includes(randomNum) === false) { resultNumArr.push(randomNum); } @@ -49,8 +49,12 @@ export default function BaseballGame() { // 3) 숫자 범위 검사 for (let i = 0; i < userInputNumbers.length; i++) { - if (userInputNumbers[i] > 0 && userInputNumbers[i] < 10) { - return true; + if ( + userInputNumbers[i] < 1 || + userInputNumbers[i] > 9 || + isNaN(userInputNumbers[i]) + ) { + return false; } } return true; From 60d0ec28aa70994b117f25ad2b7b8feff915c47c Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Thu, 28 May 2026 14:46:24 +0900 Subject: [PATCH 09/32] =?UTF-8?q?refactor:=20=EB=AC=B8=EB=B2=95=20?= =?UTF-8?q?=EB=B0=8F=20=EC=9E=90=EC=9E=98=ED=95=9C=20=EC=98=A4=EB=A5=98=20?= =?UTF-8?q?=EC=88=98=EC=A0=95(1=EC=A3=BC=EC=B0=A8=20=EC=B5=9C=EC=A2=85)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/index.js | 28 ++++++++++++++-------------- 1 file changed, 14 insertions(+), 14 deletions(-) diff --git a/src/index.js b/src/index.js index 75cef88..09a966c 100644 --- a/src/index.js +++ b/src/index.js @@ -1,12 +1,12 @@ // 게임 객체 import -import BaseballGame from "./BaseballGame"; +import BaseballGame from "./BaseballGame.js"; // 이벤트가 필요한 HTML 요소(DOM)들을 미리 정의 // 입력창, 확인 버튼, 결과를 보여줄 공간 -const userInputNum = document.querySelector(#user-input); -const submitButton = document.querySelector(#submit); -const resultDiv = document.querySelector(#result); -const reStartButton = document.querySelector(#game-restart-button); +const userInputNum = document.querySelector("#user-input"); +const submitButton = document.querySelector("#submit"); +const resultDiv = document.querySelector("#result"); +const reStartButton = document.querySelector("#game-restart-button"); // 게임 객체 생성 const game = new BaseballGame(); @@ -19,9 +19,9 @@ function initGame() { // 1. 정답 교체 computerNumbers = game.getResultNum(); // 2. 입력창에 적힌 숫자 지우기 - submitButton.value=''; + userInputNum.value = ""; // 3. 결과창 텍스트 비우기 - resultDiv.innerHTML=''; + resultDiv.innerHTML = ""; // 4. 재시작 버튼 숨기기 reStartButton.hidden = true; } @@ -30,11 +30,11 @@ function initGame() { initGame(); // 재시작 버튼 클릭 이벤트 리스너 -reStartButton.addEventListener("click", ()=> { +reStartButton.addEventListener("click", () => { // 이미 확인 버튼 이벤트 리스너에서 정답일 경우 반환 결과를 처리했음 // 그러니까 여기서는 그냥 초기화 함수만 실행해주면 됨 initGame(); -}) +}); // 확인 버튼 클릭 이벤트 리스너 submitButton.addEventListener("click", (event) => { @@ -42,19 +42,19 @@ submitButton.addEventListener("click", (event) => { // 1) 유저가 입력한 글자 가져오기 // split으로 문자열 배열로 바꿔주고, map(Number)로 숫자 배열로 변환 - userInputValue = userInputNum.value.split('').map(Number); + const userInputValue = userInputNum.value.split("").map(Number); // 2) game.isVaild()로 중복없는 1~9 세자리 숫자인지 검사 // 3) 검사 통과 못하면 alert()로 경고창 띄우고 다시 입력 - if (game.isVaild(userInputValue) === false){ + if (game.isVaild(userInputValue) === false) { alert("1~9까지의 수를 중복없이 3개 입력해주세요."); - userInputNum.value=''; // 입력창 비우기 + userInputNum.value = ""; // 입력창 비우기 userInputNum.focus(); // 커서 깜빡임 return; } // 4) 통과하면 game.play(정답값, 입력값) 실행 // 배열로 처리하기로 했으니까 사용자 입력값도 문자열에서 배열로 변환 필요 - const resultString = game.play(computerNumbers,userInputValue); + const resultString = game.play(computerNumbers, userInputValue); // 5) 반환된 결과를 출력 // 정답일 경우 / 아닌 경우 분기 @@ -67,4 +67,4 @@ submitButton.addEventListener("click", (event) => { } else { resultDiv.innerHTML = resultString; } -}); \ No newline at end of file +}); From 4088b5f6d4cb21febefa8299caf57e69068fb8ce Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Thu, 28 May 2026 15:36:05 +0900 Subject: [PATCH 10/32] =?UTF-8?q?chore:=20package-lock.json=20=EC=97=85?= =?UTF-8?q?=EB=8D=B0=EC=9D=B4=ED=8A=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- package-lock.json | 1121 +++++++++++++++++++++++++++++++-------------- 1 file changed, 778 insertions(+), 343 deletions(-) diff --git a/package-lock.json b/package-lock.json index ba10868..deccb35 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,15 +1,27 @@ { "name": "javascript-baseball-precourse", "version": "1.0.0", - "lockfileVersion": 1, + "lockfileVersion": 3, "requires": true, - "dependencies": { - "@cypress/request": { + "packages": { + "": { + "name": "javascript-baseball-precourse", + "version": "1.0.0", + "license": "MIT", + "devDependencies": { + "cypress": "8.7.0" + }, + "engines": { + "node": ">=14.0.0", + "npm": ">=6.0.0" + } + }, + "node_modules/@cypress/request": { "version": "2.88.7", "resolved": "https://registry.npmjs.org/@cypress/request/-/request-2.88.7.tgz", "integrity": "sha512-FTULIP2rnDJvZDT9t6B4nSfYR40ue19tVmv3wUcY05R9/FPCoMl1nAPJkzWzBCo7ltVn5ThQTbxiMoGBN7k0ig==", "dev": true, - "requires": { + "dependencies": { "aws-sign2": "~0.7.0", "aws4": "^1.8.0", "caseless": "~0.12.0", @@ -29,374 +41,481 @@ "tough-cookie": "~2.5.0", "tunnel-agent": "^0.6.0", "uuid": "^8.3.2" + }, + "engines": { + "node": ">= 6" } }, - "@cypress/xvfb": { + "node_modules/@cypress/xvfb": { "version": "1.2.4", "resolved": "https://registry.npmjs.org/@cypress/xvfb/-/xvfb-1.2.4.tgz", "integrity": "sha512-skbBzPggOVYCbnGgV+0dmBdW/s77ZkAOXIC1knS8NagwDjBrNC1LuXtQJeiN6l+m7lzmHtaoUw/ctJKdqkG57Q==", "dev": true, - "requires": { + "dependencies": { "debug": "^3.1.0", "lodash.once": "^4.1.1" - }, + } + }, + "node_modules/@cypress/xvfb/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, "dependencies": { - "debug": { - "version": "3.2.7", - "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", - "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", - "dev": true, - "requires": { - "ms": "^2.1.1" - } - } + "ms": "^2.1.1" } }, - "@types/node": { + "node_modules/@types/node": { "version": "14.17.34", "resolved": "https://registry.npmjs.org/@types/node/-/node-14.17.34.tgz", "integrity": "sha512-USUftMYpmuMzeWobskoPfzDi+vkpe0dvcOBRNOscFrGxVp4jomnRxWuVohgqBow2xyIPC0S3gjxV/5079jhmDg==", "dev": true }, - "@types/sinonjs__fake-timers": { + "node_modules/@types/sinonjs__fake-timers": { "version": "6.0.4", "resolved": "https://registry.npmjs.org/@types/sinonjs__fake-timers/-/sinonjs__fake-timers-6.0.4.tgz", "integrity": "sha512-IFQTJARgMUBF+xVd2b+hIgXWrZEjND3vJtRCvIelcFB5SIXfjV4bOHbHJ0eXKh+0COrBRc8MqteKAz/j88rE0A==", "dev": true }, - "@types/sizzle": { + "node_modules/@types/sizzle": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/@types/sizzle/-/sizzle-2.3.3.tgz", "integrity": "sha512-JYM8x9EGF163bEyhdJBpR2QX1R5naCJHC8ucJylJ3w9/CVBaskdQ8WqBf8MmQrd1kRvp/a4TS8HJ+bxzR7ZJYQ==", "dev": true }, - "@types/yauzl": { + "node_modules/@types/yauzl": { "version": "2.9.2", "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.2.tgz", "integrity": "sha512-8uALY5LTvSuHgloDVUvWP3pIauILm+8/0pDMokuDYIoNsOkSwd5AiHBTSEJjKTDcZr5z8UpgOWZkxBF4iJftoA==", "dev": true, "optional": true, - "requires": { + "dependencies": { "@types/node": "*" } }, - "aggregate-error": { + "node_modules/aggregate-error": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", "dev": true, - "requires": { + "dependencies": { "clean-stack": "^2.0.0", "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "ajv": { + "node_modules/ajv": { "version": "6.12.6", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "dev": true, - "requires": { + "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" } }, - "ansi-colors": { + "node_modules/ansi-colors": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz", "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "ansi-escapes": { + "node_modules/ansi-escapes": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", "dev": true, - "requires": { + "dependencies": { "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "ansi-regex": { + "node_modules/ansi-regex": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "ansi-styles": { + "node_modules/ansi-styles": { "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", "dev": true, - "requires": { + "dependencies": { "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, - "arch": { + "node_modules/arch": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/arch/-/arch-2.2.0.tgz", "integrity": "sha512-Of/R0wqp83cgHozfIYLbBMnej79U/SVGOOyuB3VVFv1NRM/PSFMK12x9KVtiYzJqmnU5WR2qp0Z5rHb7sWGnFQ==", - "dev": true + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "asn1": { + "node_modules/asn1": { "version": "0.2.6", "resolved": "https://registry.npmjs.org/asn1/-/asn1-0.2.6.tgz", "integrity": "sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==", "dev": true, - "requires": { + "dependencies": { "safer-buffer": "~2.1.0" } }, - "assert-plus": { + "node_modules/assert-plus": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz", "integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.8" + } }, - "astral-regex": { + "node_modules/astral-regex": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "async": { + "node_modules/async": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/async/-/async-3.2.2.tgz", "integrity": "sha512-H0E+qZaDEfx/FY4t7iLRv1W2fFI6+pyCeTw1uN20AQPiwqwM6ojPxHxdLv4z8hi2DtnW9BOckSspLucW7pIE5g==", "dev": true }, - "asynckit": { + "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha1-x57Zf380y48robyXkLzDZkdLS3k=", "dev": true }, - "at-least-node": { + "node_modules/at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", - "dev": true + "dev": true, + "engines": { + "node": ">= 4.0.0" + } }, - "aws-sign2": { + "node_modules/aws-sign2": { "version": "0.7.0", "resolved": "https://registry.npmjs.org/aws-sign2/-/aws-sign2-0.7.0.tgz", "integrity": "sha1-tG6JCTSpWR8tL2+G1+ap8bP+dqg=", - "dev": true + "dev": true, + "engines": { + "node": "*" + } }, - "aws4": { + "node_modules/aws4": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/aws4/-/aws4-1.11.0.tgz", "integrity": "sha512-xh1Rl34h6Fi1DC2WWKfxUTVqRsNnr6LsKz2+hfwDxQJWmrx8+c7ylaqBMcHfl1U1r2dsifOvKX3LQuLNZ+XSvA==", "dev": true }, - "balanced-match": { + "node_modules/balanced-match": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "dev": true }, - "bcrypt-pbkdf": { + "node_modules/bcrypt-pbkdf": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/bcrypt-pbkdf/-/bcrypt-pbkdf-1.0.2.tgz", "integrity": "sha1-pDAdOJtqQ/m2f/PKEaP2Y342Dp4=", "dev": true, - "requires": { + "dependencies": { "tweetnacl": "^0.14.3" } }, - "blob-util": { + "node_modules/blob-util": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/blob-util/-/blob-util-2.0.2.tgz", "integrity": "sha512-T7JQa+zsXXEa6/8ZhHcQEW1UFfVM49Ts65uBkFL6fz2QmrElqmbajIDJvuA0tEhRe5eIjpV9ZF+0RfZR9voJFQ==", "dev": true }, - "bluebird": { + "node_modules/bluebird": { "version": "3.7.2", "resolved": "https://registry.npmjs.org/bluebird/-/bluebird-3.7.2.tgz", "integrity": "sha512-XpNj6GDQzdfW+r2Wnn7xiSAd7TM3jzkxGXBGTtWKuSXv1xUV+azxAm8jdWZN06QTQk+2N2XB9jRDkvbmQmcRtg==", "dev": true }, - "brace-expansion": { + "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", "dev": true, - "requires": { + "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, - "buffer-crc32": { + "node_modules/buffer-crc32": { "version": "0.2.13", "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true + "dev": true, + "engines": { + "node": "*" + } }, - "cachedir": { + "node_modules/cachedir": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/cachedir/-/cachedir-2.3.0.tgz", "integrity": "sha512-A+Fezp4zxnit6FanDmv9EqXNAi3vt9DWp51/71UEhXukb7QUuvtv9344h91dyAxuTLoSYJFU299qzR3tzwPAhw==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "caseless": { + "node_modules/caseless": { "version": "0.12.0", "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw=", "dev": true }, - "chalk": { + "node_modules/chalk": { "version": "4.1.2", "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", "dev": true, - "requires": { + "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chalk/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, "dependencies": { - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" } }, - "check-more-types": { + "node_modules/check-more-types": { "version": "2.24.0", "resolved": "https://registry.npmjs.org/check-more-types/-/check-more-types-2.24.0.tgz", "integrity": "sha1-FCD/sQ/URNz8ebQ4kbv//TKoRgA=", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.8.0" + } }, - "ci-info": { + "node_modules/ci-info": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.2.0.tgz", "integrity": "sha512-dVqRX7fLUm8J6FgHJ418XuIgDLZDkYcDFTeL6TA2gt5WlIZUQrrH6EZrNClwT/H0FateUsZkGIOPRrLbP+PR9A==", "dev": true }, - "clean-stack": { + "node_modules/clean-stack": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "cli-cursor": { + "node_modules/cli-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", "dev": true, - "requires": { + "dependencies": { "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" } }, - "cli-table3": { + "node_modules/cli-table3": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.6.0.tgz", "integrity": "sha512-gnB85c3MGC7Nm9I/FkiasNBOKjOiO1RNuXXarQms37q4QMpWdlbBgD/VnOStA2faG1dpXMv31RFApjX1/QdgWQ==", "dev": true, - "requires": { - "colors": "^1.1.2", + "dependencies": { "object-assign": "^4.1.0", "string-width": "^4.2.0" + }, + "engines": { + "node": "10.* || >= 12.*" + }, + "optionalDependencies": { + "colors": "^1.1.2" } }, - "cli-truncate": { + "node_modules/cli-truncate": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/cli-truncate/-/cli-truncate-2.1.0.tgz", "integrity": "sha512-n8fOixwDD6b/ObinzTrp1ZKFzbgvKZvuz/TvejnLn1aQfC6r52XEx85FmuC+3HI+JM7coBRXUvNqEU2PHVrHpg==", "dev": true, - "requires": { + "dependencies": { "slice-ansi": "^3.0.0", "string-width": "^4.2.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "clone": { + "node_modules/clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", "integrity": "sha1-G39Ln1kfHo+DZwQBYANFoCiHQ18=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.8" + } }, - "color-convert": { + "node_modules/color-convert": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", "dev": true, - "requires": { + "dependencies": { "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" } }, - "color-name": { + "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", "dev": true }, - "colorette": { + "node_modules/colorette": { "version": "2.0.16", "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.16.tgz", "integrity": "sha512-hUewv7oMjCp+wkBv5Rm0v87eJhq4woh5rSR+42YSQJKecCqgIqNkZ6lAlQms/BwHPJA5NKMRlpxPRv0n8HQW6g==", "dev": true }, - "colors": { + "node_modules/colors": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", "dev": true, - "optional": true + "optional": true, + "engines": { + "node": ">=0.1.90" + } }, - "combined-stream": { + "node_modules/combined-stream": { "version": "1.0.8", "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, - "requires": { + "dependencies": { "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" } }, - "commander": { + "node_modules/commander": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/commander/-/commander-5.1.0.tgz", "integrity": "sha512-P0CysNDQ7rtVw4QIQtm+MRxV66vKFSvlsQvGYXZWR3qFU0jlMKHZZZgw8e+8DSah4UDKMqnknRDQz+xuQXQ/Zg==", - "dev": true + "dev": true, + "engines": { + "node": ">= 6" + } }, - "common-tags": { + "node_modules/common-tags": { "version": "1.8.2", "resolved": "https://registry.npmjs.org/common-tags/-/common-tags-1.8.2.tgz", "integrity": "sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==", - "dev": true + "dev": true, + "engines": { + "node": ">=4.0.0" + } }, - "concat-map": { + "node_modules/concat-map": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=", "dev": true }, - "core-util-is": { + "node_modules/core-util-is": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz", "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac=", "dev": true }, - "cross-spawn": { + "node_modules/cross-spawn": { "version": "7.0.3", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", "dev": true, - "requires": { + "dependencies": { "path-key": "^3.1.0", "shebang-command": "^2.0.0", "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" } }, - "cypress": { + "node_modules/cypress": { "version": "8.7.0", "resolved": "https://registry.npmjs.org/cypress/-/cypress-8.7.0.tgz", "integrity": "sha512-b1bMC3VQydC6sXzBMFnSqcvwc9dTZMgcaOzT0vpSD+Gq1yFc+72JDWi55sfUK5eIeNLAtWOGy1NNb6UlhMvB+Q==", "dev": true, - "requires": { + "hasInstallScript": true, + "dependencies": { "@cypress/request": "^2.88.6", "@cypress/xvfb": "^1.2.4", "@types/node": "^14.14.31", @@ -439,98 +558,122 @@ "untildify": "^4.0.0", "url": "^0.11.0", "yauzl": "^2.10.0" + }, + "bin": { + "cypress": "bin/cypress" + }, + "engines": { + "node": ">=12.0.0" } }, - "dashdash": { + "node_modules/dashdash": { "version": "1.14.1", "resolved": "https://registry.npmjs.org/dashdash/-/dashdash-1.14.1.tgz", "integrity": "sha1-hTz6D3y+L+1d4gMmuN1YEDX24vA=", "dev": true, - "requires": { + "dependencies": { "assert-plus": "^1.0.0" + }, + "engines": { + "node": ">=0.10" } }, - "dayjs": { + "node_modules/dayjs": { "version": "1.10.7", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.10.7.tgz", "integrity": "sha512-P6twpd70BcPK34K26uJ1KT3wlhpuOAPoMwJzpsIWUxHZ7wpmbdZL/hQqBDfz7hGurYSa5PhzdhDHtt319hL3ig==", "dev": true }, - "debug": { + "node_modules/debug": { "version": "4.3.2", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.2.tgz", "integrity": "sha512-mOp8wKcvj7XxC78zLgw/ZA+6TSgkoE2C/ienthhRD298T7UNwAg9diBpLRxC0mOezLl4B0xV7M0cCO6P/O0Xhw==", "dev": true, - "requires": { + "dependencies": { "ms": "2.1.2" }, - "dependencies": { - "ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "dev": true + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true } } }, - "delayed-stream": { + "node_modules/debug/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/delayed-stream": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.4.0" + } }, - "ecc-jsbn": { + "node_modules/ecc-jsbn": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/ecc-jsbn/-/ecc-jsbn-0.1.2.tgz", "integrity": "sha1-OoOpBOVDUyh4dMVkt1SThoSamMk=", "dev": true, - "requires": { + "dependencies": { "jsbn": "~0.1.0", "safer-buffer": "^2.1.0" } }, - "emoji-regex": { + "node_modules/emoji-regex": { "version": "8.0.0", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", "dev": true }, - "end-of-stream": { + "node_modules/end-of-stream": { "version": "1.4.4", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz", "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==", "dev": true, - "requires": { + "dependencies": { "once": "^1.4.0" } }, - "enquirer": { + "node_modules/enquirer": { "version": "2.3.6", "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz", "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==", "dev": true, - "requires": { + "dependencies": { "ansi-colors": "^4.1.1" + }, + "engines": { + "node": ">=8.6" } }, - "escape-string-regexp": { + "node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.8.0" + } }, - "eventemitter2": { + "node_modules/eventemitter2": { "version": "6.4.5", "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.5.tgz", "integrity": "sha512-bXE7Dyc1i6oQElDG0jMRZJrRAn9QR2xyyFGmBdZleNmyQX0FqGYmhZIrIrpPfm/w//LTo4tVQGOGQcGCb5q9uw==", "dev": true }, - "execa": { + "node_modules/execa": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/execa/-/execa-4.1.0.tgz", "integrity": "sha512-j5W0//W7f8UxAn8hXVnwG8tLwdiUy4FJLcSupCg6maBYZDpyBvTApK7KyuI4bKj8KOh1r2YH+6ucuYtJv1bTZA==", "dev": true, - "requires": { + "dependencies": { "cross-spawn": "^7.0.0", "get-stream": "^5.0.0", "human-signals": "^1.1.1", @@ -540,348 +683,461 @@ "onetime": "^5.1.0", "signal-exit": "^3.0.2", "strip-final-newline": "^2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" } }, - "executable": { + "node_modules/executable": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/executable/-/executable-4.1.1.tgz", "integrity": "sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==", "dev": true, - "requires": { + "dependencies": { "pify": "^2.2.0" + }, + "engines": { + "node": ">=4" } }, - "extend": { + "node_modules/extend": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz", "integrity": "sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==", "dev": true }, - "extract-zip": { + "node_modules/extract-zip": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz", "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==", "dev": true, - "requires": { - "@types/yauzl": "^2.9.1", + "dependencies": { "debug": "^4.1.1", "get-stream": "^5.1.0", "yauzl": "^2.10.0" + }, + "bin": { + "extract-zip": "cli.js" + }, + "engines": { + "node": ">= 10.17.0" + }, + "optionalDependencies": { + "@types/yauzl": "^2.9.1" } }, - "extsprintf": { + "node_modules/extsprintf": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/extsprintf/-/extsprintf-1.3.0.tgz", "integrity": "sha1-lpGEQOMEGnpBT4xS48V06zw+HgU=", - "dev": true + "dev": true, + "engines": [ + "node >=0.6.0" + ] }, - "fast-deep-equal": { + "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true }, - "fast-json-stable-stringify": { + "node_modules/fast-json-stable-stringify": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", "dev": true }, - "fd-slicer": { + "node_modules/fd-slicer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz", "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=", "dev": true, - "requires": { + "dependencies": { "pend": "~1.2.0" } }, - "figures": { + "node_modules/figures": { "version": "3.2.0", "resolved": "https://registry.npmjs.org/figures/-/figures-3.2.0.tgz", "integrity": "sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==", "dev": true, - "requires": { + "dependencies": { "escape-string-regexp": "^1.0.5" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "forever-agent": { + "node_modules/forever-agent": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/forever-agent/-/forever-agent-0.6.1.tgz", "integrity": "sha1-+8cfDEGt6zf5bFd60e1C2P2sypE=", - "dev": true + "dev": true, + "engines": { + "node": "*" + } }, - "form-data": { + "node_modules/form-data": { "version": "2.3.3", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.3.3.tgz", "integrity": "sha512-1lLKB2Mu3aGP1Q/2eCOx0fNbRMe7XdwktwOruhfqqd0rIJWwN4Dh+E3hrPSlDCXnSR7UtZ1N38rVXm+6+MEhJQ==", "dev": true, - "requires": { + "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.6", "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 0.12" } }, - "fs-extra": { + "node_modules/fs-extra": { "version": "9.1.0", "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, - "requires": { + "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" + }, + "engines": { + "node": ">=10" } }, - "fs.realpath": { + "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8=", "dev": true }, - "get-stream": { + "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==", "dev": true, - "requires": { + "dependencies": { "pump": "^3.0.0" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "getos": { + "node_modules/getos": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/getos/-/getos-3.2.1.tgz", "integrity": "sha512-U56CfOK17OKgTVqozZjUKNdkfEv6jk5WISBJ8SHoagjE6L69zOwl3Z+O8myjY9MEW3i2HPWQBt/LTbCgcC973Q==", "dev": true, - "requires": { + "dependencies": { "async": "^3.2.0" } }, - "getpass": { + "node_modules/getpass": { "version": "0.1.7", "resolved": "https://registry.npmjs.org/getpass/-/getpass-0.1.7.tgz", "integrity": "sha1-Xv+OPmhNVprkyysSgmBOi6YhSfo=", "dev": true, - "requires": { + "dependencies": { "assert-plus": "^1.0.0" } }, - "glob": { + "node_modules/glob": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz", "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==", + "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "requires": { + "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.0.4", "once": "^1.3.0", "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "global-dirs": { + "node_modules/global-dirs": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/global-dirs/-/global-dirs-3.0.0.tgz", "integrity": "sha512-v8ho2DS5RiCjftj1nD9NmnfaOzTdud7RRnVd9kFNOjqZbISlx5DQ+OrTkywgd0dIt7oFCvKetZSHoHcP3sDdiA==", "dev": true, - "requires": { + "dependencies": { "ini": "2.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "graceful-fs": { + "node_modules/graceful-fs": { "version": "4.2.8", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.8.tgz", "integrity": "sha512-qkIilPUYcNhJpd33n0GBXTB1MMPp14TxEsEs0pTrsSVucApsYzW5V+Q8Qxhik6KU3evy+qkAAowTByymK0avdg==", "dev": true }, - "har-schema": { + "node_modules/har-schema": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/har-schema/-/har-schema-2.0.0.tgz", "integrity": "sha1-qUwiJOvKwEeCoNkDVSHyRzW37JI=", - "dev": true + "dev": true, + "engines": { + "node": ">=4" + } }, - "har-validator": { + "node_modules/har-validator": { "version": "5.1.5", "resolved": "https://registry.npmjs.org/har-validator/-/har-validator-5.1.5.tgz", "integrity": "sha512-nmT2T0lljbxdQZfspsno9hgrG3Uir6Ks5afism62poxqBM6sDnMEuPmzTq8XN0OEwqKLLdh1jQI3qyE66Nzb3w==", + "deprecated": "this library is no longer supported", "dev": true, - "requires": { + "dependencies": { "ajv": "^6.12.3", "har-schema": "^2.0.0" + }, + "engines": { + "node": ">=6" } }, - "has-flag": { + "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "http-signature": { + "node_modules/http-signature": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/http-signature/-/http-signature-1.2.0.tgz", "integrity": "sha1-muzZJRFHcvPZW2WmCruPfBj7rOE=", "dev": true, - "requires": { + "dependencies": { "assert-plus": "^1.0.0", "jsprim": "^1.2.2", "sshpk": "^1.7.0" + }, + "engines": { + "node": ">=0.8", + "npm": ">=1.3.7" } }, - "human-signals": { + "node_modules/human-signals": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-1.1.1.tgz", "integrity": "sha512-SEQu7vl8KjNL2eoGBLF3+wAjpsNfA9XMlXAYj/3EdaNfAlxKthD1xjEQfGOUhllCGGJVNY34bRr6lPINhNjyZw==", - "dev": true + "dev": true, + "engines": { + "node": ">=8.12.0" + } }, - "indent-string": { + "node_modules/indent-string": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "inflight": { + "node_modules/inflight": { "version": "1.0.6", "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", "dev": true, - "requires": { + "dependencies": { "once": "^1.3.0", "wrappy": "1" } }, - "inherits": { + "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, - "ini": { + "node_modules/ini": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ini/-/ini-2.0.0.tgz", "integrity": "sha512-7PnF4oN3CvZF23ADhA5wRaYEQpJ8qygSkbtTXWBeXWXmEVRXK+1ITciHWwHhsjv1TmW0MgacIv6hEi5pX5NQdA==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + } }, - "is-ci": { + "node_modules/is-ci": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/is-ci/-/is-ci-3.0.1.tgz", "integrity": "sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==", "dev": true, - "requires": { + "dependencies": { "ci-info": "^3.2.0" + }, + "bin": { + "is-ci": "bin.js" } }, - "is-fullwidth-code-point": { + "node_modules/is-fullwidth-code-point": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "is-installed-globally": { + "node_modules/is-installed-globally": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/is-installed-globally/-/is-installed-globally-0.4.0.tgz", "integrity": "sha512-iwGqO3J21aaSkC7jWnHP/difazwS7SFeIqxv6wEtLU8Y5KlzFTjyqcSIT0d8s4+dDhKytsk9PJZ2BkS5eZwQRQ==", "dev": true, - "requires": { + "dependencies": { "global-dirs": "^3.0.0", "is-path-inside": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "is-path-inside": { + "node_modules/is-path-inside": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "is-stream": { + "node_modules/is-stream": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "is-typedarray": { + "node_modules/is-typedarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/is-typedarray/-/is-typedarray-1.0.0.tgz", "integrity": "sha1-5HnICFjfDBsR3dppQPlgEfzaSpo=", "dev": true }, - "is-unicode-supported": { + "node_modules/is-unicode-supported": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "isexe": { + "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", "dev": true }, - "isstream": { + "node_modules/isstream": { "version": "0.1.2", "resolved": "https://registry.npmjs.org/isstream/-/isstream-0.1.2.tgz", "integrity": "sha1-R+Y/evVa+m+S4VAOaQ64uFKcCZo=", "dev": true }, - "jsbn": { + "node_modules/jsbn": { "version": "0.1.1", "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-0.1.1.tgz", "integrity": "sha1-peZUwuWi3rXyAdls77yoDA7y9RM=", "dev": true }, - "json-schema": { + "node_modules/json-schema": { "version": "0.2.3", "resolved": "https://registry.npmjs.org/json-schema/-/json-schema-0.2.3.tgz", "integrity": "sha1-tIDIkuWaLwWVTOcnvT8qTogvnhM=", "dev": true }, - "json-schema-traverse": { + "node_modules/json-schema-traverse": { "version": "0.4.1", "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", "dev": true }, - "json-stringify-safe": { + "node_modules/json-stringify-safe": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/json-stringify-safe/-/json-stringify-safe-5.0.1.tgz", "integrity": "sha1-Epai1Y/UXxmg9s4B1lcB4sc1tus=", "dev": true }, - "jsonfile": { + "node_modules/jsonfile": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.1.0.tgz", "integrity": "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ==", "dev": true, - "requires": { - "graceful-fs": "^4.1.6", + "dependencies": { "universalify": "^2.0.0" + }, + "optionalDependencies": { + "graceful-fs": "^4.1.6" } }, - "jsprim": { + "node_modules/jsprim": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/jsprim/-/jsprim-1.4.1.tgz", "integrity": "sha1-MT5mvB5cwG5Di8G3SZwuXFastqI=", "dev": true, - "requires": { + "engines": [ + "node >=0.6.0" + ], + "dependencies": { "assert-plus": "1.0.0", "extsprintf": "1.3.0", "json-schema": "0.2.3", "verror": "1.10.0" } }, - "lazy-ass": { + "node_modules/lazy-ass": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/lazy-ass/-/lazy-ass-1.6.0.tgz", "integrity": "sha1-eZllXoZGwX8In90YfRUNMyTVRRM=", - "dev": true + "dev": true, + "engines": { + "node": "> 0.8" + } }, - "listr2": { + "node_modules/listr2": { "version": "3.13.4", "resolved": "https://registry.npmjs.org/listr2/-/listr2-3.13.4.tgz", "integrity": "sha512-lZ1Rut1DSIRwbxQbI8qaUBfOWJ1jEYRgltIM97j6kKOCI2pHVWMyxZvkU/JKmRBWcIYgDS2PK+yDgVqm7u3crw==", "dev": true, - "requires": { + "dependencies": { "cli-truncate": "^2.1.0", "clone": "^2.1.2", "colorette": "^2.0.16", @@ -890,331 +1146,449 @@ "rxjs": "^7.4.0", "through": "^2.3.8", "wrap-ansi": "^7.0.0" + }, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "enquirer": ">= 2.3.0 < 3" + }, + "peerDependenciesMeta": { + "enquirer": { + "optional": true + } } }, - "lodash": { + "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", "dev": true }, - "lodash.once": { + "node_modules/lodash.once": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", "integrity": "sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=", "dev": true }, - "log-symbols": { + "node_modules/log-symbols": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", "dev": true, - "requires": { + "dependencies": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "log-update": { + "node_modules/log-update": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/log-update/-/log-update-4.0.0.tgz", "integrity": "sha512-9fkkDevMefjg0mmzWFBW8YkFP91OrizzkW3diF7CpG+S2EYdy4+TVfGwz1zeF8x7hCx1ovSPTOE9Ngib74qqUg==", "dev": true, - "requires": { + "dependencies": { "ansi-escapes": "^4.3.0", "cli-cursor": "^3.1.0", "slice-ansi": "^4.0.0", "wrap-ansi": "^6.2.0" }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-update/node_modules/slice-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", + "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "astral-regex": "^2.0.0", + "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/slice-ansi?sponsor=1" + } + }, + "node_modules/log-update/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "dev": true, "dependencies": { - "slice-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", - "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "astral-regex": "^2.0.0", - "is-fullwidth-code-point": "^3.0.0" - } - }, - "wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "dev": true, - "requires": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - } - } + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" } }, - "merge-stream": { + "node_modules/merge-stream": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", "dev": true }, - "mime-db": { + "node_modules/mime-db": { "version": "1.51.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.51.0.tgz", "integrity": "sha512-5y8A56jg7XVQx2mbv1lu49NR4dokRnhZYTtL+KGfaa27uq4pSTXkwQkFJl4pkRMyNFz/EtYDSkiiEHx3F7UN6g==", - "dev": true + "dev": true, + "engines": { + "node": ">= 0.6" + } }, - "mime-types": { + "node_modules/mime-types": { "version": "2.1.34", "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.34.tgz", "integrity": "sha512-6cP692WwGIs9XXdOO4++N+7qjqv0rqxxVvJ3VHPh/Sc9mVZcQP+ZGhkKiTvWMQRr2tbHkJP/Yn7Y0npb3ZBs4A==", "dev": true, - "requires": { + "dependencies": { "mime-db": "1.51.0" + }, + "engines": { + "node": ">= 0.6" } }, - "mimic-fn": { + "node_modules/mimic-fn": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "minimatch": { + "node_modules/minimatch": { "version": "3.0.4", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz", "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==", "dev": true, - "requires": { + "dependencies": { "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" } }, - "minimist": { + "node_modules/minimist": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, - "ms": { + "node_modules/ms": { "version": "2.1.3", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, - "npm-run-path": { + "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", "dev": true, - "requires": { + "dependencies": { "path-key": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "object-assign": { + "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "once": { + "node_modules/once": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=", "dev": true, - "requires": { + "dependencies": { "wrappy": "1" } }, - "onetime": { + "node_modules/onetime": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", "dev": true, - "requires": { + "dependencies": { "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "ospath": { + "node_modules/ospath": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/ospath/-/ospath-1.2.2.tgz", "integrity": "sha1-EnZjl3Sj+O8lcvf+QoDg6kVQwHs=", "dev": true }, - "p-map": { + "node_modules/p-map": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", "dev": true, - "requires": { + "dependencies": { "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" } }, - "path-is-absolute": { + "node_modules/path-is-absolute": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "path-key": { + "node_modules/path-key": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "pend": { + "node_modules/pend": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz", "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA=", "dev": true }, - "performance-now": { + "node_modules/performance-now": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/performance-now/-/performance-now-2.1.0.tgz", "integrity": "sha1-Ywn04OX6kT7BxpMHrjZLSzd8nns=", "dev": true }, - "pify": { + "node_modules/pify": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", "integrity": "sha1-7RQaasBDqEnqWISY59yosVMw6Qw=", - "dev": true + "dev": true, + "engines": { + "node": ">=0.10.0" + } }, - "pretty-bytes": { + "node_modules/pretty-bytes": { "version": "5.6.0", "resolved": "https://registry.npmjs.org/pretty-bytes/-/pretty-bytes-5.6.0.tgz", "integrity": "sha512-FFw039TmrBqFK8ma/7OL3sDz/VytdtJr044/QUJtH0wK9lb9jLq9tJyIxUwtQJHwar2BqtiA4iCWSwo9JLkzFg==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "proxy-from-env": { + "node_modules/proxy-from-env": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.0.0.tgz", "integrity": "sha1-M8UDmPcOp+uW0h97gXYwpVeRx+4=", "dev": true }, - "psl": { + "node_modules/psl": { "version": "1.8.0", "resolved": "https://registry.npmjs.org/psl/-/psl-1.8.0.tgz", "integrity": "sha512-RIdOzyoavK+hA18OGGWDqUTsCLhtA7IcZ/6NCs4fFJaHBDab+pDDmDIByWFRQJq2Cd7r1OoQxBGKOaztq+hjIQ==", "dev": true }, - "pump": { + "node_modules/pump": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz", "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==", "dev": true, - "requires": { + "dependencies": { "end-of-stream": "^1.1.0", "once": "^1.3.1" } }, - "punycode": { + "node_modules/punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "qs": { + "node_modules/qs": { "version": "6.5.2", "resolved": "https://registry.npmjs.org/qs/-/qs-6.5.2.tgz", "integrity": "sha512-N5ZAX4/LxJmF+7wN74pUD6qAh9/wnvdQcjq9TZjevvXzSUo7bfmw91saqMjzGS2xq91/odN2dW/WOl7qQHNDGA==", - "dev": true + "dev": true, + "engines": { + "node": ">=0.6" + } }, - "querystring": { + "node_modules/querystring": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/querystring/-/querystring-0.2.0.tgz", "integrity": "sha1-sgmEkgO7Jd+CDadW50cAWHhSFiA=", - "dev": true + "deprecated": "The querystring API is considered Legacy. new code should use the URLSearchParams API instead.", + "dev": true, + "engines": { + "node": ">=0.4.x" + } }, - "ramda": { + "node_modules/ramda": { "version": "0.27.1", "resolved": "https://registry.npmjs.org/ramda/-/ramda-0.27.1.tgz", "integrity": "sha512-PgIdVpn5y5Yns8vqb8FzBUEYn98V3xcPgawAkkgj0YJ0qDsnHCiNmZYfOGMgOvoB0eWFLpYbhxUR3mxfDIMvpw==", "dev": true }, - "request-progress": { + "node_modules/request-progress": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/request-progress/-/request-progress-3.0.0.tgz", "integrity": "sha1-TKdUCBx/7GP1BeT6qCWqBs1mnb4=", "dev": true, - "requires": { + "dependencies": { "throttleit": "^1.0.0" } }, - "restore-cursor": { + "node_modules/restore-cursor": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", "dev": true, - "requires": { + "dependencies": { "onetime": "^5.1.0", "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" } }, - "rimraf": { + "node_modules/rimraf": { "version": "3.0.2", "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", "dev": true, - "requires": { + "dependencies": { "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" } }, - "rxjs": { + "node_modules/rxjs": { "version": "7.4.0", "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.4.0.tgz", "integrity": "sha512-7SQDi7xeTMCJpqViXh8gL/lebcwlp3d831F05+9B44A4B0WfsEwUQHR64gsH1kvJ+Ep/J9K2+n1hVl1CsGN23w==", "dev": true, - "requires": { + "dependencies": { "tslib": "~2.1.0" } }, - "safe-buffer": { + "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "dev": true + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] }, - "safer-buffer": { + "node_modules/safer-buffer": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", "dev": true }, - "shebang-command": { + "node_modules/shebang-command": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", "dev": true, - "requires": { + "dependencies": { "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "shebang-regex": { + "node_modules/shebang-regex": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "signal-exit": { + "node_modules/signal-exit": { "version": "3.0.6", "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.6.tgz", "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", "dev": true }, - "slice-ansi": { + "node_modules/slice-ansi": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-3.0.0.tgz", "integrity": "sha512-pSyv7bSTC7ig9Dcgbw9AuRNUb5k5V6oDudjZoMBSr13qpLBG7tB+zgCkARjq7xIUgdz5P1Qe8u+rSGdouOOIyQ==", "dev": true, - "requires": { + "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", "is-fullwidth-code-point": "^3.0.0" + }, + "engines": { + "node": ">=8" } }, - "sshpk": { + "node_modules/sshpk": { "version": "1.16.1", "resolved": "https://registry.npmjs.org/sshpk/-/sshpk-1.16.1.tgz", "integrity": "sha512-HXXqVUq7+pcKeLqqZj6mHFUMvXtOJt1uoUx09pFW6011inTMxqI8BA8PM95myrIyyKwdnzjdFjLiE6KBPVtJIg==", "dev": true, - "requires": { + "dependencies": { "asn1": "~0.2.3", "assert-plus": "^1.0.0", "bcrypt-pbkdf": "^1.0.0", @@ -1224,189 +1598,250 @@ "jsbn": "~0.1.0", "safer-buffer": "^2.0.2", "tweetnacl": "~0.14.0" + }, + "bin": { + "sshpk-conv": "bin/sshpk-conv", + "sshpk-sign": "bin/sshpk-sign", + "sshpk-verify": "bin/sshpk-verify" + }, + "engines": { + "node": ">=0.10.0" } }, - "string-width": { + "node_modules/string-width": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "dev": true, - "requires": { + "dependencies": { "emoji-regex": "^8.0.0", "is-fullwidth-code-point": "^3.0.0", "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" } }, - "strip-ansi": { + "node_modules/strip-ansi": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", "dev": true, - "requires": { + "dependencies": { "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" } }, - "strip-final-newline": { + "node_modules/strip-final-newline": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", - "dev": true + "dev": true, + "engines": { + "node": ">=6" + } }, - "supports-color": { + "node_modules/supports-color": { "version": "8.1.1", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, - "requires": { + "dependencies": { "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/supports-color?sponsor=1" } }, - "throttleit": { + "node_modules/throttleit": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/throttleit/-/throttleit-1.0.0.tgz", "integrity": "sha1-nnhYNtr0Z0MUWlmEtiaNgoUorGw=", "dev": true }, - "through": { + "node_modules/through": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz", "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU=", "dev": true }, - "tmp": { + "node_modules/tmp": { "version": "0.2.1", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.1.tgz", "integrity": "sha512-76SUhtfqR2Ijn+xllcI5P1oyannHNHByD80W1q447gU3mp9G9PSpGdWmjUOHRDPiHYacIk66W7ubDTuPF3BEtQ==", "dev": true, - "requires": { + "dependencies": { "rimraf": "^3.0.0" + }, + "engines": { + "node": ">=8.17.0" } }, - "tough-cookie": { + "node_modules/tough-cookie": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-2.5.0.tgz", "integrity": "sha512-nlLsUzgm1kfLXSXfRZMc1KLAugd4hqJHDTvc2hDIwS3mZAfMEuMbc03SujMF+GEcpaX/qboeycw6iO8JwVv2+g==", "dev": true, - "requires": { + "dependencies": { "psl": "^1.1.28", "punycode": "^2.1.1" + }, + "engines": { + "node": ">=0.8" } }, - "tslib": { + "node_modules/tslib": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.1.0.tgz", "integrity": "sha512-hcVC3wYEziELGGmEEXue7D75zbwIIVUMWAVbHItGPx0ziyXxrOMQx4rQEVEV45Ut/1IotuEvwqPopzIOkDMf0A==", "dev": true }, - "tunnel-agent": { + "node_modules/tunnel-agent": { "version": "0.6.0", "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", "integrity": "sha1-J6XeoGs2sEoKmWZ3SykIaPD8QP0=", "dev": true, - "requires": { + "dependencies": { "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" } }, - "tweetnacl": { + "node_modules/tweetnacl": { "version": "0.14.5", "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-0.14.5.tgz", "integrity": "sha1-WuaBd/GS1EViadEIr6k/+HQ/T2Q=", "dev": true }, - "type-fest": { + "node_modules/type-fest": { "version": "0.21.3", "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", - "dev": true + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } }, - "universalify": { + "node_modules/universalify": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.0.tgz", "integrity": "sha512-hAZsKq7Yy11Zu1DE0OzWjw7nnLZmJZYTDZZyEFHZdUhV8FkH5MCfoU1XMaxXovpyW5nq5scPqq0ZDP9Zyl04oQ==", - "dev": true + "dev": true, + "engines": { + "node": ">= 10.0.0" + } }, - "untildify": { + "node_modules/untildify": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/untildify/-/untildify-4.0.0.tgz", "integrity": "sha512-KK8xQ1mkzZeg9inewmFVDNkg3l5LUhoq9kN6iWYB/CC9YMG8HA+c1Q8HwDe6dEX7kErrEVNVBO3fWsVq5iDgtw==", - "dev": true + "dev": true, + "engines": { + "node": ">=8" + } }, - "uri-js": { + "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", "dev": true, - "requires": { + "dependencies": { "punycode": "^2.1.0" } }, - "url": { + "node_modules/url": { "version": "0.11.0", "resolved": "https://registry.npmjs.org/url/-/url-0.11.0.tgz", "integrity": "sha1-ODjpfPxgUh63PFJajlW/3Z4uKPE=", "dev": true, - "requires": { + "dependencies": { "punycode": "1.3.2", "querystring": "0.2.0" - }, - "dependencies": { - "punycode": { - "version": "1.3.2", - "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", - "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", - "dev": true - } } }, - "uuid": { + "node_modules/url/node_modules/punycode": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.3.2.tgz", + "integrity": "sha1-llOgNvt8HuQjQvIyXM7v6jkmxI0=", + "dev": true + }, + "node_modules/uuid": { "version": "8.3.2", "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz", "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==", - "dev": true + "deprecated": "uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).", + "dev": true, + "bin": { + "uuid": "dist/bin/uuid" + } }, - "verror": { + "node_modules/verror": { "version": "1.10.0", "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", "dev": true, - "requires": { + "engines": [ + "node >=0.6.0" + ], + "dependencies": { "assert-plus": "^1.0.0", "core-util-is": "1.0.2", "extsprintf": "^1.2.0" } }, - "which": { + "node_modules/which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", "dev": true, - "requires": { + "dependencies": { "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" } }, - "wrap-ansi": { + "node_modules/wrap-ansi": { "version": "7.0.0", "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz", "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==", "dev": true, - "requires": { + "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/wrap-ansi?sponsor=1" } }, - "wrappy": { + "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=", "dev": true }, - "yauzl": { + "node_modules/yauzl": { "version": "2.10.0", "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", "dev": true, - "requires": { + "dependencies": { "buffer-crc32": "~0.2.3", "fd-slicer": "~1.1.0" } From 771e62eb898d180e844ba2d8b74cc78248d806b1 Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Thu, 28 May 2026 15:59:02 +0900 Subject: [PATCH 11/32] =?UTF-8?q?refactor:=20BaseballGame=20=ED=95=A8?= =?UTF-8?q?=EC=88=98=EC=9D=98=20play=20=EB=A9=94=EC=84=9C=EB=93=9C?= =?UTF-8?q?=EC=97=90=EC=84=9C=20=EB=AC=B8=EC=9E=90=EC=97=B4=20=EB=B3=80?= =?UTF-8?q?=ED=99=98=20=EB=A1=9C=EC=A7=81=20=EC=A0=9C=EA=B1=B0=20(MVC=20?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=A0=81=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BaseballGame.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/BaseballGame.js b/src/BaseballGame.js index 3b97ac8..6b3ed6c 100644 --- a/src/BaseballGame.js +++ b/src/BaseballGame.js @@ -12,6 +12,8 @@ export default function BaseballGame() { } } + /* + 문자열 변환은 Controller의 역할 const returnString = []; if (ballCnt > 0) returnString.push(`${ballCnt}볼`); if (strikeCnt > 0) returnString.push(`${strikeCnt}스트라이크`); @@ -19,7 +21,10 @@ export default function BaseballGame() { return "낫싱"; } else { return returnString.join(" "); - } + } */ + + // js에서는 return A,B 형태 사용 불가 - , 마지막 값만 평가 -> 객체로 묶어줘야 함. + return { strike: strikeCnt, ball: ballCnt }; }; // 2. 정답값 생성 (랜덤) From d9a66fb44f8e6e9128a2b97f2c40bf25db94ab0d Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Thu, 28 May 2026 17:15:00 +0900 Subject: [PATCH 12/32] =?UTF-8?q?refactor:=20View.js=20=EC=83=9D=EC=84=B1?= =?UTF-8?q?=20(MVC=20=ED=8C=A8=ED=84=B4=20=EC=A0=81=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/View.js | 56 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/View.js diff --git a/src/View.js b/src/View.js new file mode 100644 index 0000000..926a2ca --- /dev/null +++ b/src/View.js @@ -0,0 +1,56 @@ +export default function View() { + // 이벤트가 필요한 HTML 요소(DOM) 정의 + // 입력창, 확인 버튼, 결과를 보여줄 공간 + const userInputNum = document.querySelector("#user-input"); + const submitButton = document.querySelector("#submit"); + const resultDiv = document.querySelector("#result"); + const reStartButton = document.querySelector("#game-restart-button"); + + // 유저 입력값 전달 + this.getInputValue = function () { + // 데이터 가공은 컨트롤러가 함. + return userInputNum.value; + }; + + // 에러 처리 - alert() + this.renderError = function (errMessage) { + alert(errMessage); + userInputNum.value = ""; // 입력창 비우기 + userInputNum.focus(); // 커서 깜빡임 + }; + + // 컨트롤러가 넘겨준 결과(3스트라이크)를 화면에 그리기 + this.renderResult = function (resultString) { + if (resultString === "3스트라이크") { + resultDiv.innerHTML = ` +

정답!

+

게임을 새로 시작하시겠어요?

+ `; + reStartButton.hidden = false; + } else { + resultDiv.innerHTML = resultString; + } + }; + + // 초기화 화면 세팅 (정답값 받아오는 건 컨트롤러 역할) + this.resetGame = function () { + userInputNum.value = ""; + resultDiv.innerHTML = ""; + reStartButton.hidden = true; + }; + + // 이벤트 연결 (확인, 재시작 버튼) + // 콜백 함수 + this.bindSubmitEvent = function (handler) { + this.submitButton.addEventListener("click", (event) => { + event.preventDefault(); + handler(); + }); + }; + + this.bindResetEvent = function (handler) { + this.reStartButton.addEventListener("click", () => { + handler(); + }); + }; +} From 5a91c51b3842ada178df0a8e8fa6a7f468c85324 Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Thu, 28 May 2026 17:15:52 +0900 Subject: [PATCH 13/32] =?UTF-8?q?refactor:=20index.js=20=EB=84=A4=EC=9D=B4?= =?UTF-8?q?=EB=B0=8D=20Controller.js=EB=A1=9C=20=EB=B3=80=EA=B2=BD=20?= =?UTF-8?q?=EB=B0=8F=20View=EC=99=80=20Controller=20=EC=97=AD=ED=95=A0=20?= =?UTF-8?q?=EB=B6=84=EB=A6=AC=20(MVC=20=ED=8C=A8=ED=84=B4=20=EC=A0=81?= =?UTF-8?q?=EC=9A=A9)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Controller.js | 53 +++++++++++++++++++++++++++++++++++ src/index.js | 70 ----------------------------------------------- 2 files changed, 53 insertions(+), 70 deletions(-) create mode 100644 src/Controller.js delete mode 100644 src/index.js diff --git a/src/Controller.js b/src/Controller.js new file mode 100644 index 0000000..48ae7f2 --- /dev/null +++ b/src/Controller.js @@ -0,0 +1,53 @@ +// 게임 객체 import +import BaseballGame from "./BaseballGame.js"; +import View from "./View.js"; + +// 게임 객체 생성 +const game = new BaseballGame(); + +// 정답 데이터 저장할 공간 생성 +let computerNumbers = []; + +// 초기화 함수 +function initGame() { + // 정답 교체 + computerNumbers = game.getResultNum(); + View.resetGame(); +} + +// 처음 시작 할 때 한 번 실행 +initGame(); + +// 재시작 버튼 클릭 이벤트 리스너 +View.bindResetEvent(initGame); + +// 확인 버튼 클릭 이벤트 리스너 +View.bindSubmitEvent(() => { + // 문자열 배열로 바꿔주기 + const userInputValue = View.getInputValue.split("").map(Number); + + // 유효값 검사 후 에러처리 + if (game.isVaild(userInputValue) === false) { + View.renderError("1~9까지의 수를 중복없이 3개 입력해주세요."); + return; + } + // 통과 시 게임 실행 + // 객체로 결과 받아옴 + const { strike, ball } = game.play(computerNumbers, userInputValue); + + // 받아온 숫자 데이터 기반 텍스트 조립 + const returnStringArr = []; + if (ball > 0) returnStringArr.push(`${ball}볼`); + if (strike > 0) returnStringArr.push(`${strike}스트라이크`); + + // 최종 문자열 + const finalResultString = ""; + if (finalResultString.length === 0) { + ("낫싱"); + } else { + finalResultString = returnStringArr.join(" "); + } + + // 반환 결과 출력 + View.renderResult(finalResultString); +}); diff --git a/src/index.js b/src/index.js deleted file mode 100644 index 09a966c..0000000 --- a/src/index.js +++ /dev/null @@ -1,70 +0,0 @@ -// 게임 객체 import -import BaseballGame from "./BaseballGame.js"; - -// 이벤트가 필요한 HTML 요소(DOM)들을 미리 정의 -// 입력창, 확인 버튼, 결과를 보여줄 공간 -const userInputNum = document.querySelector("#user-input"); -const submitButton = document.querySelector("#submit"); -const resultDiv = document.querySelector("#result"); -const reStartButton = document.querySelector("#game-restart-button"); - -// 게임 객체 생성 -const game = new BaseballGame(); - -// 정답 데이터 저장할 공간 생성 -let computerNumbers = []; - -// 초기화 함수 -function initGame() { - // 1. 정답 교체 - computerNumbers = game.getResultNum(); - // 2. 입력창에 적힌 숫자 지우기 - userInputNum.value = ""; - // 3. 결과창 텍스트 비우기 - resultDiv.innerHTML = ""; - // 4. 재시작 버튼 숨기기 - reStartButton.hidden = true; -} - -// 처음 시작 할 때 한 번 실행 -initGame(); - -// 재시작 버튼 클릭 이벤트 리스너 -reStartButton.addEventListener("click", () => { - // 이미 확인 버튼 이벤트 리스너에서 정답일 경우 반환 결과를 처리했음 - // 그러니까 여기서는 그냥 초기화 함수만 실행해주면 됨 - initGame(); -}); - -// 확인 버튼 클릭 이벤트 리스너 -submitButton.addEventListener("click", (event) => { - event.preventDefault(); // 폼 제출 시 새로고침 막기 - - // 1) 유저가 입력한 글자 가져오기 - // split으로 문자열 배열로 바꿔주고, map(Number)로 숫자 배열로 변환 - const userInputValue = userInputNum.value.split("").map(Number); - - // 2) game.isVaild()로 중복없는 1~9 세자리 숫자인지 검사 - // 3) 검사 통과 못하면 alert()로 경고창 띄우고 다시 입력 - if (game.isVaild(userInputValue) === false) { - alert("1~9까지의 수를 중복없이 3개 입력해주세요."); - userInputNum.value = ""; // 입력창 비우기 - userInputNum.focus(); // 커서 깜빡임 - return; - } - // 4) 통과하면 game.play(정답값, 입력값) 실행 - // 배열로 처리하기로 했으니까 사용자 입력값도 문자열에서 배열로 변환 필요 - const resultString = game.play(computerNumbers, userInputValue); - - // 5) 반환된 결과를 출력 - // 정답일 경우 / 아닌 경우 분기 - if (resultString === "3스트라이크") { - resultDiv.innerHTML = ` -

정답!

-

게임을 새로 시작하시겠어요?

- `; - reStartButton.hidden = false; - } else { - resultDiv.innerHTML = resultString; - } -}); From 2405a5b6a14151dbe81156ee4942555bf2670565 Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Thu, 28 May 2026 17:18:28 +0900 Subject: [PATCH 14/32] =?UTF-8?q?fix:=20View=20=EC=9D=B8=EC=8A=A4=ED=84=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Controller.js | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/Controller.js b/src/Controller.js index 48ae7f2..02579f3 100644 --- a/src/Controller.js +++ b/src/Controller.js @@ -2,8 +2,9 @@ import BaseballGame from "./BaseballGame.js"; import View from "./View.js"; -// 게임 객체 생성 +// 게임 객체 생성 및 뷰 인스턴스 생성 const game = new BaseballGame(); +const view = new View(); // 정답 데이터 저장할 공간 생성 let computerNumbers = []; @@ -12,23 +13,23 @@ let computerNumbers = []; function initGame() { // 정답 교체 computerNumbers = game.getResultNum(); - View.resetGame(); + view.resetGame(); } // 처음 시작 할 때 한 번 실행 initGame(); // 재시작 버튼 클릭 이벤트 리스너 -View.bindResetEvent(initGame); +view.bindResetEvent(initGame); // 확인 버튼 클릭 이벤트 리스너 -View.bindSubmitEvent(() => { +view.bindSubmitEvent(() => { // 문자열 배열로 바꿔주기 - const userInputValue = View.getInputValue.split("").map(Number); + const userInputValue = view.getInputValue.split("").map(Number); // 유효값 검사 후 에러처리 if (game.isVaild(userInputValue) === false) { - View.renderError("1~9까지의 수를 중복없이 3개 입력해주세요."); + view.renderError("1~9까지의 수를 중복없이 3개 입력해주세요."); return; } // 통과 시 게임 실행 @@ -49,5 +50,5 @@ View.bindSubmitEvent(() => { } // 반환 결과 출력 - View.renderResult(finalResultString); + view.renderResult(finalResultString); }); From afbfa33f494642af59b2413cd8d86444fa85f829 Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Thu, 28 May 2026 17:19:03 +0900 Subject: [PATCH 15/32] =?UTF-8?q?fix:=20getInputValue=20=EB=A9=94=EC=84=9C?= =?UTF-8?q?=EB=93=9C=20=ED=98=95=ED=83=9C=EB=A1=9C=20=ED=98=B8=EC=B6=9C=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controller.js b/src/Controller.js index 02579f3..940e7bb 100644 --- a/src/Controller.js +++ b/src/Controller.js @@ -25,7 +25,7 @@ view.bindResetEvent(initGame); // 확인 버튼 클릭 이벤트 리스너 view.bindSubmitEvent(() => { // 문자열 배열로 바꿔주기 - const userInputValue = view.getInputValue.split("").map(Number); + const userInputValue = view.getInputValue().split("").map(Number); // 유효값 검사 후 에러처리 if (game.isVaild(userInputValue) === false) { From 6173de9e779c4d111d1aa37cba6bc47721f9adfa Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Thu, 28 May 2026 17:23:38 +0900 Subject: [PATCH 16/32] =?UTF-8?q?fix:=20=EC=B5=9C=EC=A2=85=20=EB=AC=B8?= =?UTF-8?q?=EC=9E=90=EC=97=B4=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95=20?= =?UTF-8?q?(let=20=EC=82=AC=EC=9A=A9,=20finalResultString=20=EA=B0=92=20?= =?UTF-8?q?=ED=95=A0=EB=8B=B9=20=EC=88=98=EC=A0=95,=20=EB=B0=B0=EC=97=B4?= =?UTF-8?q?=EC=9D=98=20=EA=B8=B8=EC=9D=B4=EB=A5=BC=20=EA=B2=80=EC=82=AC?= =?UTF-8?q?=ED=95=B4=EC=95=BC=20=ED=95=A8.=20=EC=88=98=EC=A0=95)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Controller.js | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/src/Controller.js b/src/Controller.js index 940e7bb..76652e9 100644 --- a/src/Controller.js +++ b/src/Controller.js @@ -42,9 +42,10 @@ view.bindSubmitEvent(() => { if (strike > 0) returnStringArr.push(`${strike}스트라이크`); // 최종 문자열 - const finalResultString = ""; - if (finalResultString.length === 0) { - ("낫싱"); + // 값을 변경해야하므로 let으로 선언해야 함 + let finalResultString = ""; + if (returnStringArr.length === 0) { + finalResultString = "낫싱"; } else { finalResultString = returnStringArr.join(" "); } From 31e0e15c9379bab3b213f4c345af67ec7d813341 Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Thu, 28 May 2026 17:34:20 +0900 Subject: [PATCH 17/32] =?UTF-8?q?refactor:=20DOM=20=EC=9A=94=EC=86=8C=20co?= =?UTF-8?q?nst=20=EB=A1=9C=EC=BB=AC=20=20=EB=B3=80=EC=88=98=20=EC=84=A0?= =?UTF-8?q?=EC=96=B8=20->=20this.=20=EC=9D=B8=EC=8A=A4=ED=84=B4=EC=8A=A4?= =?UTF-8?q?=20=EC=86=8D=EC=84=B1=20=EC=A0=95=EC=9D=98=20=EB=B0=A9=EC=8B=9D?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/View.js | 27 ++++++++++++++------------- 1 file changed, 14 insertions(+), 13 deletions(-) diff --git a/src/View.js b/src/View.js index 926a2ca..32579a4 100644 --- a/src/View.js +++ b/src/View.js @@ -1,42 +1,43 @@ export default function View() { // 이벤트가 필요한 HTML 요소(DOM) 정의 // 입력창, 확인 버튼, 결과를 보여줄 공간 - const userInputNum = document.querySelector("#user-input"); - const submitButton = document.querySelector("#submit"); - const resultDiv = document.querySelector("#result"); - const reStartButton = document.querySelector("#game-restart-button"); + this.userInputNum = document.querySelector("#user-input"); + this.submitButton = document.querySelector("#submit"); + this.resultDiv = document.querySelector("#result"); + this.reStartButton = document.querySelector("#game-restart-button"); // 유저 입력값 전달 this.getInputValue = function () { // 데이터 가공은 컨트롤러가 함. - return userInputNum.value; + // 내부 변수가 아니기 때문에 여기서도 this. 필요 + return this.userInputNum.value; }; // 에러 처리 - alert() this.renderError = function (errMessage) { alert(errMessage); - userInputNum.value = ""; // 입력창 비우기 - userInputNum.focus(); // 커서 깜빡임 + this.userInputNum.value = ""; // 입력창 비우기 + this.userInputNum.focus(); // 커서 깜빡임 }; // 컨트롤러가 넘겨준 결과(3스트라이크)를 화면에 그리기 this.renderResult = function (resultString) { if (resultString === "3스트라이크") { - resultDiv.innerHTML = ` + this.resultDiv.innerHTML = `

정답!

게임을 새로 시작하시겠어요?

`; - reStartButton.hidden = false; + this.reStartButton.hidden = false; } else { - resultDiv.innerHTML = resultString; + this.resultDiv.innerHTML = resultString; } }; // 초기화 화면 세팅 (정답값 받아오는 건 컨트롤러 역할) this.resetGame = function () { - userInputNum.value = ""; - resultDiv.innerHTML = ""; - reStartButton.hidden = true; + this.userInputNum.value = ""; + this.resultDiv.innerHTML = ""; + this.reStartButton.hidden = true; }; // 이벤트 연결 (확인, 재시작 버튼) From 25f6ce13774dfe1e9941bfcb1e35a1a691b9dbba Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Thu, 28 May 2026 19:47:37 +0900 Subject: [PATCH 18/32] =?UTF-8?q?chore:=20=ED=8C=8C=EC=9D=BC=20=EC=9D=B4?= =?UTF-8?q?=EB=A6=84=20=EB=B3=80=EA=B2=BD=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/index.html b/index.html index 9e258dc..4a60668 100644 --- a/index.html +++ b/index.html @@ -23,6 +23,6 @@

📄 결과

- + From f6d2d61d85570cf057de9d37267f4e97325c11fe Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Thu, 28 May 2026 19:48:14 +0900 Subject: [PATCH 19/32] =?UTF-8?q?docs:=20=EA=B0=9C=EC=9D=B8=20=EB=AF=B8?= =?UTF-8?q?=EC=85=98=20=EC=88=98=ED=96=89=20README.md=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 339 +++++++++++++++++++++++++++++++++++------------------- 1 file changed, 222 insertions(+), 117 deletions(-) diff --git a/README.md b/README.md index 2e2a22d..e43216d 100644 --- a/README.md +++ b/README.md @@ -1,150 +1,255 @@ -

- -

-

숫자 야구 게임

+# 숫자 야구 게임 -## 🔍 진행방식 +## 개인 목표 및 목표 달성을 위한 행동 가이드 -- 미션은 **기능 요구사항, 프로그래밍 요구사항, 과제 진행 요구사항** 세 가지로 구성되어 있다. -- 세 개의 요구사항을 만족하기 위해 노력한다. 특히 기능을 구현하기 전에 기능 목록을 만들고, 기능 단위로 커밋 하는 방식으로 진행한다. +### 미션 전체 공통 목표 + +- 로직을 이해하고, AI의 도움 없이도 스스로 문제를 해결한다. +- 문제를 해결하는 여러 가지 방안을 고민해본 후 차이점과 장단점을 비교한다. +- 몰랐던 개념이나 새롭게 알게 된 부분을 더 깊게 찾아보고 정리한다. +- AI와 스터디를 활용해 리팩토링한다. + +### 이번 미션 목표 + +- HTML, CSS, JavaScript의 기본 문법을 알고, 미션에 적용해 체득한다. +- 몰랐던 부분, 헷갈리는 부분, 새롭게 알게 된 사실을 파악하여 확실하게 짚고 넘어간다. +- 1주차 미션인 기능 구현과 2주차 미션인 MVC 패턴 적용으로 나누어 README를 작성하고, 코드는 기능 및 순서별로 커밋을 통해 기록을 남긴다. + +--- + +## 기능 구현 목록 + +### 1주차 미션: 숫자 야구 게임을 바닐라 JS로 기능 구현하기 + +2주차 미션에서 새롭게 추가한 기능은 없으므로 해당 파트는 1주차만 기록한다. + +
+공통 요구사항 + +1. 순수 Vanilla JS로만 구현할 것 +2. 인덴트 depth가 2를 넘지 않도록 구현할 것 +3. 함수가 한 가지 일만 하도록 최대한 작게 만들 것 +4. 변수 선언 시 `var`를 사용하지 않을 것 +5. 함수 길이가 15라인을 넘어가지 않을 것 + +
+ +
+기능 요구사항 + +- 1~9 중복 없는 3자리 수를 맞추는 게임 +- 컴퓨터가 1~9의 서로 다른 랜덤한 수 3개를 선택 + - 랜덤 값은 반드시 `Random.pickNumberInRange`를 사용해 구현 + - `MissionUtils` 라이브러리는 전역 객체에 추가되어 있으므로 `import` 하지 않아도 됨 + - 랜덤값 세 자리는 랜덤 함수를 세 번 호출해서 한 자리 수씩 확정 +- 결과값 규칙 + - 수 O, 자리 O : 스트라이크 + - 수 O, 자리 X : 볼 + - 모든 수 X : 낫싱 +- 사용자가 값을 입력하면 결과값 출력 + - 정답이면 재시작 버튼 노출 +- 사용자가 잘못된 값을 입력한 경우 `alert`로 에러 메시지 표시 후 재입력 + +
+ +
+프로그래밍 요구사항 + +- `play(컴퓨터_랜덤값, 유저_입력값)` 메서드를 만들어서 게임 진행 +- `play` 메서드는 `String`으로 결과값 반환 + - 스트라이크와 볼이 함께 있는 경우 볼 먼저 출력 + +
+ +### 구현 기능 목록 + +**1. 확인 버튼 클릭 시** + +- 유효성 검사 후 에러 처리 (alert 표시 및 입력창 초기화) +- 결과값 출력 +- 정답(3스트라이크) 시 재시작 버튼 노출 + +**2. 랜덤 정답 생성** + +- `MissionUtils.Random.pickNumberInRange(1, 9)`를 세 번 호출하여 한 자리씩 확정 +- 중복 없는 3자리 배열 반환 + +**3. 결과 판별 (`play` 메서드)** + +- 스트라이크/볼/낫싱 판별 후 객체로 반환 + +**4. 유효성 검사 (`isValid` 메서드)** + +- 길이, 중복, 숫자 범위(1~9) 검사 + +**5. 재시작 버튼 클릭 시** + +- 정답 새로 생성, 결과창 초기화, 재시작 버튼 hidden 처리 + +--- + +## 학습 내용 + +### 1주차 미션: 숫자 야구 게임을 바닐라 JS로 기능 구현하기 + +**생성자 함수와 객체 (Constructor Function)** + +`this.play = function(...)`과 같은 생성자 함수 패턴은 단순 함수와 달리 객체를 생성한다. `BaseballGame()`을 생성자 함수로 정의하면 정답 숫자를 상태로 보관하고 `play`, `getResultNum`, `isValid` 메서드가 해당 데이터를 활용하는 캡슐화 구조를 만들 수 있다. + +**DOM 조작** + +바닐라 JS에서는 `document.querySelector`로 HTML 요소를 직접 가져와 이벤트와 상태를 수동으로 연결해야 한다. CSS 선택자 규칙에 따라 id는 `#`, class는 `.`, 태그 이름은 그대로 사용한다. + +`innerHTML`에 HTML 문자열을 통째로 덮어씌우는 방식으로 화면을 갱신할 수 있으며, `input` 태그의 값은 `.value`로 접근한다. + +**`innerHTML`로 버튼을 동적 생성 vs `hidden` 속성 or `display: none`** + +`innerHTML`로 버튼을 동적 생성하면 DOM에 요소가 존재하지 않는 초기 시점에 이벤트 리스너를 등록할 수 없다는 문제가 생긴다. HTML에 미리 `hidden` 속성을 부여해두거나 CSS의 `display: none`속성을 적용하면 DOM에는 존재하면서 화면에만 보이지 않으므로, 이벤트 리스너를 초기에 등록하고 필요 시 `hidden = false`로 전환하는 방식이 더 안정적이다. + +**`event.preventDefault()`** + +`
` 안의 버튼 클릭은 기본적으로 페이지를 새로고침한다. 이벤트 핸들러 내에서 `event.preventDefault()`를 호출하여 기본 동작을 막아야 한다. + +**배열 내장 메서드** + +- `Array.prototype.includes()`: 배열 안에 특정 값이 포함되어 있는지 `boolean`으로 반환한다. 중복 없는 랜덤 배열 생성 시 `push` 조건 검사에 활용했다. +- `String.prototype.split('')`: 문자열을 한 글자씩 잘라 배열로 반환한다. +- `Array.prototype.map(Number)`: 배열 요소를 숫자 타입으로 변환한다. 입력값이 문자열로 들어오므로 정답 숫자 배열과 `===` 비교 시 타입 불일치 문제를 방지하기 위해 반드시 필요하다. + +**`Set` 자료구조** + +`new Set(array)`는 중복 값을 제거한 집합을 생성한다. `.size`로 고유 요소의 수를 확인할 수 있어, 유효성 검사의 중복 검사 로직에 활용했다. + +**인덴트 depth 계산 기준** + +depth는 파일 전체가 아닌 각 함수(또는 메서드)의 블록을 기준으로 독립적으로 계산된다. + +**`return` 시 복수 값 반환** + +JavaScript에서 `return a, b` 형태는 쉼표 연산자에 의해 마지막 값만 반환된다. 복수의 값을 반환하려면 객체 `{ strike, ball }` 형태로 묶어야 하며, 호출부에서는 구조 분해 할당 `const { strike, ball } = game.play(...)`으로 값을 꺼낼 수 있다. + +--- + +### 2주차 미션: 1주차 미션에서 구현한 코드에 MVC 패턴을 적용하기 + +**MVC 패턴 개념** + +- **Model**: 데이터 처리 및 상태 관리 로직 +- **View**: 사용자에게 보이는 화면 (DOM 조작 코드) +- **Controller**: 사용자 입력 처리, Model 업데이트, View 제어 + +JavaScript에서 View는 단순한 HTML 파일이 아니라, DOM을 조작하는 JavaScript 코드를 의미한다. + +**역할 분리 기준** + +| 파일 | 역할 | +| ---------------------------- | ------------------------------------------------------------------- | +| `BaseballGame.js` (Model) | 정답 생성, 스트라이크/볼/낫싱 판별, 유효성 검사. 순수 데이터만 반환 | +| `View.js` (View) | DOM 요소 정의, 입력값 전달, 결과/에러 렌더링, 이벤트 바인딩 | +| `Controller.js` (Controller) | Model과 View 객체 생성 및 연결, 흐름 제어, 결과 문자열 조립 | + +Controller는 HTML 구조(id, class 등)를 알아서는 안 된다. DOM에 직접 접근하는 순간 MVC의 분리 원칙이 깨진다. + +**콜백 함수를 통한 이벤트 바인딩** + +이벤트 리스너를 Controller에서 직접 등록하면 Controller가 DOM을 알아야 한다. 이를 방지하기 위해 View에 `bindSubmitEvent(handler)`와 같은 메서드를 만들고, Controller가 실행할 로직(콜백 함수)을 인자로 넘겨준다. View는 버튼 클릭 타이밍만 감지하고 실행은 Controller의 함수에 위임한다. + +**`this`의 사용 범위** + +생성자 함수 내에서 내부적으로만 사용하는 변수는 `const`로 선언하면 완전히 은닉된다. `this`로 선언하면 외부에서 접근 가능하며, `console.log(view)` 시 현재 View가 보유한 DOM 요소를 확인할 수 있어 디버깅이 편리하다. 또한 나중에 ES6 `class` 문법으로 전환할 때 `this`를 사용하는 구조가 자연스럽게 호환된다. + +--- + +## 고민했던 문제와 해결 과정에서 배운 점 + +### 1주차 미션: 숫자 야구 게임을 바닐라 JS로 기능 구현하기 + +**파일 분리 방식** + +처음에는 `index.js` 하나에 모든 기능을 구현하려 했으나, `import`문을 통한 모듈화 방식을 뒤늦게 확인하여 기능별 파일 분리로 전환했다. + +**재시작 버튼 노출 방식** + +`innerHTML`로 버튼을 동적 생성하는 방법을 먼저 시도했다. 그러나 JS가 처음 실행될 때 버튼이 DOM에 존재하지 않아 이벤트 리스너 등록 타이밍이 어긋나는 문제가 발생했다. HTML에 `hidden` 속성을 미리 부여해두고 `button.hidden = false`로 전환하는 방식으로 해결했다. + +**낫싱만 출력되는 버그** + +입력값을 `split('')`으로만 처리하면 문자열 타입의 배열이 생성된다. 정답 배열은 숫자 타입이므로 `===` 비교 시 항상 불일치하여 스트라이크/볼이 0으로 나오는 문제였다. `.map(Number)`를 추가하여 타입을 일치시키는 것으로 해결했다. + +**유효성 검사 로직 오류** + +숫자 범위 검사에서 `true`를 반환하는 조건을 잘못 작성하여, 첫 번째 숫자가 유효하면 나머지를 검사하지 않고 통과시키는 문제가 발생했다. 유효성 검사 함수는 조건을 불통과할 경우 `false`를 반환하고, 모든 조건을 통과했을 때 `true`를 반환하는 구조로 수정했다. + +**우테코 라이브러리 호출 방법** + +`Random.pickNumberInRange()`로 호출했으나 동작하지 않았다. HTML에서 로드한 라이브러리는 `MissionUtils.Random`이라는 네임스페이스로 묶여 있으므로 `MissionUtils.Random.pickNumberInRange(1, 9)`로 호출해야 한다. + +**Cypress 테스트 오류** + +Cypress와 Chrome 브라우저 버전 충돌로 테스트가 실패했다. 백그라운드에서 크롬 프로세스가 잔존하여 테스트 봇의 접근을 막고 있었다. 열려 있는 창을 모두 닫고 `killall "Google Chrome"` 명령어로 프로세스를 완전히 종료한 후 재실행하여 해결했다. --- -## 🎯 기능 요구사항 +### 2주차 미션: 1주차 미션에서 구현한 코드에 MVC 패턴을 적용하기 -기본적으로 1부터 9까지 서로 다른 수로 이루어진 3자리의 수를 맞추는 게임이다. +**기존 코드의 MVC 역할 재검토** -- 같은 수가 같은 자리에 있으면 `스트라이크`, 다른 자리에 있으면 `볼`, 같은 수가 전혀 없으면 `낫싱`이란 힌트를 얻고, 그 힌트를 이용해서 먼저 상대방(컴퓨터)의 수를 맞추면 승리한다. - - 예) 상대방(컴퓨터)의 수가 425일 때 - - 123을 제시한 경우 : 1스트라이크 - - 456을 제시한 경우 : 1볼 1스트라이크 - - 789를 제시한 경우 : 낫싱 -- 위 숫자 야구게임에서 상대방의 역할을 컴퓨터가 한다. 컴퓨터는 1에서 9까지 서로 다른 임의의 수 3개를 선택한다. 게임 플레이어는 컴퓨터가 생각하고 있는 3개의 숫자를 입력하고, 컴퓨터는 입력한 숫자에 대한 결과를 출력한다. -- 이 같은 과정을 반복해 컴퓨터가 선택한 3개의 숫자를 모두 맞히면 게임이 종료되고, 재시작 버튼이 노출된다. -- 게임이 종료된 후 재시작 버튼을 클릭해 게임을 다시 시작할 수 있다. -- 사용자가 잘못된 값을 입력한 경우 `alert`으로 에러 메시지를 보여주고, 다시 입력할 수 있게 한다. +초기에는 `BaseballGame.js` = Model, `index.html` = View, `index.js` = Controller로 분리되어 있다고 판단했다. 그러나 JavaScript에서 View는 HTML 파일 자체가 아니라 DOM 조작 코드를 의미한다는 것을 파악했다. 기존 `index.js`가 Controller 역할과 View 역할을 함께 담당하고 있었으므로, `View.js`를 별도로 분리하는 방향으로 리팩토링했다. -
+**Controller에서 DOM에 직접 접근하는 문제** -### 💻 실행 결과 +이벤트 리스너를 Controller에서 직접 등록하면 `document.querySelector('#submit')`과 같이 HTML 구조를 Controller가 알아야 한다. 이는 MVC의 분리 원칙에 위배된다. View에 `bindSubmitEvent(handler)` 메서드를 만들어 Controller가 실행 로직을 콜백으로 넘겨주고, View는 클릭 감지만 담당하는 방식으로 해결했다. -![baseball_result_gif](./images/baseball_demo.gif) +**`play` 메서드 반환값 변경** -
+문자열 조립이 Model의 역할인지 Controller의 역할인지 고민했다. 문자열 조립은 표현 로직이므로 Controller에서 담당하는 것이 적절하다고 판단하여, `play` 메서드는 `{ strike, ball }` 객체를 반환하도록 수정하고 문자열 조립은 Controller에서 처리하도록 분리했다. --- -## ✅ 프로그래밍 요구사항 - -- `play`(컴퓨터의 랜덤 값, 유저의 입력 값) 메서드를 만들어 게임을 진행한다. -- `play`메서드는 `String`으로 결과값을 return 한다. - - 스트라이크와 볼이 같이 있는 경우 볼을 먼저쓰고, 스트라이크를 쓴다. -- `index.js`에서 아래의 function 또는 class 형태를 활용한다. - -```javascript -export default function BaseballGame() { - this.play = function (computerInputNumbers, userInputNumbers) { - return "결과 값 String"; - }; -} - -export default class BaseballGame { - play(computerInputNumbers, userInputNumbers) { - return "결과 값 String"; - } -} - -// 예시 -play(123, 456); // '낫싱' -play(123, 345); // '1볼' -play(123, 432); // '2볼' -play(123, 312); // '3볼' -play(123, 145); // '1스트라이크' -play(123, 134); // '1볼 1스트라이크' -play(123, 132); // '2볼 1스트라이크' -play(123, 124); // '2스트라이크' -``` - -### 라이브러리 - -- 컴퓨터의 랜덤 값은 반드시 JavaScript의 `Math.random` 대신 [`MissionUtils` 라이브러리](https://github.com/woowacourse-projects/javascript-mission-utils#mission-utils)의 `Random.pickNumberInRange`를 사용해 구한다. - - - `MissionUtils` 라이브러리 스크립트는 `index.html`에 이미 포함되어 전역 객체에 추가되어 있으므로, 따로 `import` 하지 않아도 구현 코드 어디에서든 사용할 수 있다. - - 숫자야구 게임의 세 자리 숫자를 뽑기 위한 랜덤값을 뽑을 때 `Random.pickNumberInRange`를 세 번 호출하여 한 자리 수씩 확정하는 방법으로 구한다. 한 번에 세 자리 수를 확정하는 방법은 사용하지 않으며, 이는 테스트 코드의 통과를 위함이다. - - ```javascript - // ex) - const randomNumber = Random.pickNumberInRange(1, 9); - ``` - -### 공통 요구사항 - -- 외부 라이브러리(jQuery, Lodash 등)를 사용하지 않고, 순수 Vanilla JS로만 구현한다. -- **[Airbnb 자바스크립트 코드 컨벤션](https://github.com/airbnb/javascript)을 지키면서 프로그래밍** 한다. -- **indent(인덴트, 들여쓰기) depth를 2가 넘지 않도록 구현**한다. - - 예를 들어 while문 안에 if문이 있으면 들여쓰기는 2이다. - - 힌트: indent(인덴트, 들여쓰기) depth를 줄이는 좋은 방법은 함수(또는 메소드)를 분리하면 된다. -- **함수(또는 메소드)가 한 가지 일만 하도록 최대한 작게** 만들어라. -- 변수 선언시 `var` 를 사용하지 않는다. `const` 와 `let` 을 사용한다. - - [const](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/const) - - [let](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/let) -- `import` 문을 이용해 스크립트를 모듈화하고 불러올 수 있게 만든다. - - [https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/import](https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/import) -- **함수(또는 메소드)의 길이가 15라인을 넘어가지 않도록 구현한다.** - - 함수(또는 메소드)가 한 가지 일만 잘 하도록 구현한다. - -
+## 리팩토링 + +### 1주차 미션: 숫자 야구 게임을 바닐라 JS로 기능 구현하기 + +추후 스터디 이후 작성 예정. + +아래는 검토해볼 수 있는 부분이다. + +- `isValid` 함수에서 숫자 범위 검사와 `isNaN` 검사를 분리하거나, 정규식으로 간결하게 통합하는 방안 검토 +- 유효성 검사 실패 메시지를 상수로 분리하여 관리 --- -## 📝 과제 진행 요구사항 +### 2주차 미션: 1주차 미션에서 구현한 코드에 MVC 패턴을 적용하기 -- 미션은 [javascript-baseball-precourse](https://github.com/woowacourse/javascript-baseball-precourse/) 저장소를 Fork/Clone해 시작한다. -- **기능을 구현하기 전에 javascript-baseball-precourse/docs/README.md 파일에 구현할 기능 목록을 정리**해 추가한다. -- **Git의 커밋 단위는 앞 단계에서 README.md 파일에 정리한 기능 목록 단위**로 추가한다. - - [AngularJS Commit Message Conventions](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) 참고해 commit log를 남긴다. -- 과제 진행 및 제출 방법은 [프리코스 과제 제출 문서](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 를 참고한다. +추후 스터디 이후 작성 예정. -## ✉️ 미션 제출 방법 +아래는 검토해볼 수 있는 부분이다. -- 미션 구현을 완료한 후 GitHub을 통해 제출해야 한다. - - GitHub을 활용한 제출 방법은 [프리코스 과제 제출 문서](https://github.com/woowacourse/woowacourse-docs/tree/master/precourse) 의 8번 항목까지를 참고해 제출한다. +- View의 `renderResult`에서 `"3스트라이크"` 문자열을 하드코딩으로 비교하는 부분을 상수 또는 인자(boolean)로 대체하는 방안 검토 +- Controller의 문자열 조립 로직을 별도 유틸 함수로 분리하는 방안 검토 -### ✔️ 테스트 실행 가이드 +--- -- 테스트 실행에 필요한 패키지 설치를 위해 `Node.js` 버전 `14` 이상이 필요하다. -- 다음 명령어를 입력해 패키지를 설치한다. +## 과거 코드와 비교 -```bash -// {폴더 경로}/javascript-baseball-precourse/ 에서 -npm install -``` +### 1주차 미션: 숫자 야구 게임을 바닐라 JS로 기능 구현하기 -- 설치가 완료되었다면, 다음 명령어를 입력해 테스트를 실행한다. +#### 달라진 점 -```bash -// {폴더 경로}/javascript-baseball-precourse/ 에서 -npm run test -``` +추후 작성 예정. -- 아래와 같은 화면이 나오며 모든 테스트가 pass한다면 성공! +#### 과거 코드에서 배운 점 -![테스트 결과](./images/test_result.png) +추후 작성 예정. --- -
+### 2주차 미션: 1주차 미션에서 구현한 코드에 MVC 패턴을 적용하기 + +#### 달라진 점 -## 🔗 참고 링크 +추후 작성 예정. -미션 진행이 어렵다면 아래 링크를 참고한다. +#### 과거 코드에서 배운 점 -- DOM - - [MDN DOM](https://developer.mozilla.org/ko/docs/Web/API/Document_Object_Model/%EC%86%8C%EA%B0%9C) -- alert - - [MDN alert](https://developer.mozilla.org/ko/docs/Web/API/Window/alert) -- javascript module - - [module](https://ko.javascript.info/modules-intro) -- event - - [이벤트](https://ko.javascript.info/introduction-browser-events) - - [이벤트 위임](https://ko.javascript.info/event-delegation) +추후 작성 예정. From 867a90468909e3515dec4d739463f4a429d3cd25 Mon Sep 17 00:00:00 2001 From: Kang Yeryeong Date: Fri, 29 May 2026 11:14:27 +0900 Subject: [PATCH 20/32] Update src/View.js MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 변수명 통일성 수정 Co-authored-by: Yooseong Nam <102887277+meteorqz6@users.noreply.github.com> --- src/View.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/View.js b/src/View.js index 32579a4..c9694b3 100644 --- a/src/View.js +++ b/src/View.js @@ -4,7 +4,7 @@ export default function View() { this.userInputNum = document.querySelector("#user-input"); this.submitButton = document.querySelector("#submit"); this.resultDiv = document.querySelector("#result"); - this.reStartButton = document.querySelector("#game-restart-button"); + this.restartButton = document.querySelector("#game-restart-button"); // 유저 입력값 전달 this.getInputValue = function () { From 72d466da5a35f2c3b92666cfe5f409ea1289abe1 Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Fri, 29 May 2026 11:39:02 +0900 Subject: [PATCH 21/32] =?UTF-8?q?refactor:=20DOM=20=EC=9A=94=EC=86=8C=20?= =?UTF-8?q?=EB=B3=80=EC=88=98=EB=AA=85=EC=97=90=20$=20=EC=A0=91=EB=91=90?= =?UTF-8?q?=EC=82=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/View.js | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/View.js b/src/View.js index c9694b3..2665983 100644 --- a/src/View.js +++ b/src/View.js @@ -1,56 +1,56 @@ export default function View() { // 이벤트가 필요한 HTML 요소(DOM) 정의 // 입력창, 확인 버튼, 결과를 보여줄 공간 - this.userInputNum = document.querySelector("#user-input"); - this.submitButton = document.querySelector("#submit"); - this.resultDiv = document.querySelector("#result"); - this.restartButton = document.querySelector("#game-restart-button"); + this.$userInputNum = document.querySelector("#user-input"); + this.$submitButton = document.querySelector("#submit"); + this.$resultDiv = document.querySelector("#result"); + this.$restartButton = document.querySelector("#game-restart-button"); // 유저 입력값 전달 this.getInputValue = function () { // 데이터 가공은 컨트롤러가 함. // 내부 변수가 아니기 때문에 여기서도 this. 필요 - return this.userInputNum.value; + return this.$userInputNum.value; }; // 에러 처리 - alert() this.renderError = function (errMessage) { alert(errMessage); - this.userInputNum.value = ""; // 입력창 비우기 - this.userInputNum.focus(); // 커서 깜빡임 + this.$userInputNum.value = ""; // 입력창 비우기 + this.$userInputNum.focus(); // 커서 깜빡임 }; // 컨트롤러가 넘겨준 결과(3스트라이크)를 화면에 그리기 this.renderResult = function (resultString) { if (resultString === "3스트라이크") { - this.resultDiv.innerHTML = ` + this.$resultDiv.innerHTML = `

정답!

게임을 새로 시작하시겠어요?

`; - this.reStartButton.hidden = false; + this.$restartButton.hidden = false; } else { - this.resultDiv.innerHTML = resultString; + this.$resultDiv.innerHTML = resultString; } }; // 초기화 화면 세팅 (정답값 받아오는 건 컨트롤러 역할) this.resetGame = function () { - this.userInputNum.value = ""; - this.resultDiv.innerHTML = ""; - this.reStartButton.hidden = true; + this.$userInputNum.value = ""; + this.$resultDiv.innerHTML = ""; + this.$restartButton.hidden = true; }; // 이벤트 연결 (확인, 재시작 버튼) // 콜백 함수 this.bindSubmitEvent = function (handler) { - this.submitButton.addEventListener("click", (event) => { + this.$submitButton.addEventListener("click", (event) => { event.preventDefault(); handler(); }); }; this.bindResetEvent = function (handler) { - this.reStartButton.addEventListener("click", () => { + this.$restartButton.addEventListener("click", () => { handler(); }); }; From 0ab2ef2a94072fc3ed977a528946dd63adef7227 Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Fri, 29 May 2026 11:46:03 +0900 Subject: [PATCH 22/32] =?UTF-8?q?refactor:=20=EB=A7=A4=EC=A7=81=EB=84=98?= =?UTF-8?q?=EB=B2=84=20=EC=83=81=EC=88=98=20=EB=84=A4=EC=9D=B4=EB=B0=8D=20?= =?UTF-8?q?=EC=BB=A8=EB=B2=A4=EC=85=98=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BaseballGame.js | 20 +++++++++++++------- 1 file changed, 13 insertions(+), 7 deletions(-) diff --git a/src/BaseballGame.js b/src/BaseballGame.js index 6b3ed6c..1540be5 100644 --- a/src/BaseballGame.js +++ b/src/BaseballGame.js @@ -1,10 +1,16 @@ +// 매직넘버 상수화 +const DIGIT_COUNT = 3; +const MIN_NUM = 1; +const MAX_NUM = 9; +const ERROR_MESSAGE = "1~9 사이 서로 다른 숫자 3개를 입력하세요."; + export default function BaseballGame() { // 1. 스트라이크, 볼, 낫싱 판별 this.play = function (computerInputNumbers, userInputNumbers) { let strikeCnt = 0; let ballCnt = 0; - for (let i = 0; i < 3; i++) { + for (let i = 0; i < DIGIT_COUNT; i++) { if (userInputNumbers[i] === computerInputNumbers[i]) { strikeCnt++; } else if (computerInputNumbers.includes(userInputNumbers[i])) { @@ -31,8 +37,8 @@ export default function BaseballGame() { this.getResultNum = function () { const resultNumArr = []; - while (resultNumArr.length < 3) { - const randomNum = MissionUtils.Random.pickNumberInRange(1, 9); + while (resultNumArr.length < DIGIT_COUNT) { + const randomNum = MissionUtils.Random.pickNumberInRange(MIN_NUM, MAX_NUM); if (resultNumArr.includes(randomNum) === false) { resultNumArr.push(randomNum); } @@ -43,20 +49,20 @@ export default function BaseballGame() { // 3. 유효값 판별 this.isVaild = function (userInputNumbers) { // 1) 길이 검사 - if (userInputNumbers.length !== 3) { + if (userInputNumbers.length !== DIGIT_COUNT) { return false; } // 2) 중복 검사 - if (new Set(userInputNumbers).size !== 3) { + if (new Set(userInputNumbers).size !== DIGIT_COUNT) { return false; } // 3) 숫자 범위 검사 for (let i = 0; i < userInputNumbers.length; i++) { if ( - userInputNumbers[i] < 1 || - userInputNumbers[i] > 9 || + userInputNumbers[i] < MIN_NUM || + userInputNumbers[i] > MAX_NUM || isNaN(userInputNumbers[i]) ) { return false; From 6b79a95405387d0fd22b02f16115be73388744bd Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Fri, 29 May 2026 11:46:24 +0900 Subject: [PATCH 23/32] =?UTF-8?q?refactor:=20ERROR=5FMESSAGE=20=EC=83=81?= =?UTF-8?q?=EC=88=98=20Model=EC=97=90=EC=84=9C=20export=20=EB=B0=8F=20Cont?= =?UTF-8?q?roller=20import=20=EC=A0=81=EC=9A=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BaseballGame.js | 2 +- src/Controller.js | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/BaseballGame.js b/src/BaseballGame.js index 1540be5..7aaa7c6 100644 --- a/src/BaseballGame.js +++ b/src/BaseballGame.js @@ -2,7 +2,7 @@ const DIGIT_COUNT = 3; const MIN_NUM = 1; const MAX_NUM = 9; -const ERROR_MESSAGE = "1~9 사이 서로 다른 숫자 3개를 입력하세요."; +export const ERROR_MESSAGE = "1~9 사이 서로 다른 숫자 3개를 입력하세요."; export default function BaseballGame() { // 1. 스트라이크, 볼, 낫싱 판별 diff --git a/src/Controller.js b/src/Controller.js index 76652e9..4db4c97 100644 --- a/src/Controller.js +++ b/src/Controller.js @@ -1,5 +1,5 @@ // 게임 객체 import -import BaseballGame from "./BaseballGame.js"; +import BaseballGame, { ERROR_MESSAGE } from "./BaseballGame.js"; import View from "./View.js"; // 게임 객체 생성 및 뷰 인스턴스 생성 @@ -29,7 +29,7 @@ view.bindSubmitEvent(() => { // 유효값 검사 후 에러처리 if (game.isVaild(userInputValue) === false) { - view.renderError("1~9까지의 수를 중복없이 3개 입력해주세요."); + view.renderError(ERROR_MESSAGE); return; } // 통과 시 게임 실행 From ff6373d5f36a28621920aacdf6fb79b4a8d7a670 Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Fri, 29 May 2026 14:37:34 +0900 Subject: [PATCH 24/32] =?UTF-8?q?refactor:=20Model=20=EC=9C=A0=ED=9A=A8?= =?UTF-8?q?=EA=B0=92=20=EA=B2=80=EC=A6=9D=20=EB=A1=9C=EC=A7=81=20=EA=B0=9C?= =?UTF-8?q?=EC=84=A0(=EB=AC=B8=EC=9E=90=EC=97=B4=20=ED=83=80=EC=9E=85?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EA=B2=80=EC=82=AC),=20=EC=9E=85=EB=A0=A5?= =?UTF-8?q?=EA=B0=92=EC=9D=98=20=EA=B2=80=EC=A6=9D=20=EC=9D=B4=ED=9B=84=20?= =?UTF-8?q?=EA=B0=80=EA=B3=B5=ED=95=B4=20=EA=B2=8C=EC=9E=84=20=EC=8B=A4?= =?UTF-8?q?=ED=96=89=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BaseballGame.js | 28 ++++++++++------------------ src/Controller.js | 10 +++++++--- 2 files changed, 17 insertions(+), 21 deletions(-) diff --git a/src/BaseballGame.js b/src/BaseballGame.js index 7aaa7c6..c3ee934 100644 --- a/src/BaseballGame.js +++ b/src/BaseballGame.js @@ -46,28 +46,20 @@ export default function BaseballGame() { return resultNumArr; }; - // 3. 유효값 판별 - this.isVaild = function (userInputNumbers) { + // 3. 유효값 판별 (userInputValue 타입: 문자열) + this.isVaild = function (userInputValue) { // 1) 길이 검사 - if (userInputNumbers.length !== DIGIT_COUNT) { + if (userInputValue.length !== DIGIT_COUNT) { return false; } - // 2) 중복 검사 - if (new Set(userInputNumbers).size !== DIGIT_COUNT) { - return false; - } + // 2. 숫자 범위 검사 (정규식) + if (!/^[1-9]+$/.test(userInputValue)) return false; - // 3) 숫자 범위 검사 - for (let i = 0; i < userInputNumbers.length; i++) { - if ( - userInputNumbers[i] < MIN_NUM || - userInputNumbers[i] > MAX_NUM || - isNaN(userInputNumbers[i]) - ) { - return false; - } - } - return true; + // if (!/^[1-9]{3}$/.test(input)) return false;처럼 + // 아예 정규식으로 길이와 숫자 범위를 한 번에 검사가 가능하긴 한데, 이렇게 되면 매직넘버가 돼서 DIGIT_COUNT를 상수화한 이유가 사라짐 + + // 3) 중복 제거 (Set) + return new Set(userInputValue.split("")).size === DIGIT_COUNT; }; } diff --git a/src/Controller.js b/src/Controller.js index 4db4c97..aeeadf0 100644 --- a/src/Controller.js +++ b/src/Controller.js @@ -24,17 +24,21 @@ view.bindResetEvent(initGame); // 확인 버튼 클릭 이벤트 리스너 view.bindSubmitEvent(() => { - // 문자열 배열로 바꿔주기 - const userInputValue = view.getInputValue().split("").map(Number); + // 입력값 받아오기 (타입: 문자열) + const userInputValue = view.getInputValue(); // 유효값 검사 후 에러처리 if (game.isVaild(userInputValue) === false) { view.renderError(ERROR_MESSAGE); return; } + + // 숫자 배열로 바꿔주기 (입력값 형식 가공) + const userNumbers = userInputValue.split("").map(Number); + // 통과 시 게임 실행 // 객체로 결과 받아옴 - const { strike, ball } = game.play(computerNumbers, userInputValue); + const { strike, ball } = game.play(computerNumbers, userNumbers); // 받아온 숫자 데이터 기반 텍스트 조립 const returnStringArr = []; From e7c359fa4f473ffaf9d34ee1df202d9e0a37b682 Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Fri, 29 May 2026 14:42:13 +0900 Subject: [PATCH 25/32] =?UTF-8?q?refactor:=20=EB=AC=B8=EC=9E=90=EC=97=B4?= =?UTF-8?q?=20=EB=B3=80=ED=99=98=20=EB=A1=9C=EC=A7=81=20Controller=20?= =?UTF-8?q?=E2=86=92=20Model=EB=A1=9C=20=EC=9D=B4=EB=8F=99?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BaseballGame.js | 18 +++++++----------- src/Controller.js | 19 ++----------------- 2 files changed, 9 insertions(+), 28 deletions(-) diff --git a/src/BaseballGame.js b/src/BaseballGame.js index c3ee934..28627f4 100644 --- a/src/BaseballGame.js +++ b/src/BaseballGame.js @@ -18,19 +18,15 @@ export default function BaseballGame() { } } - /* - 문자열 변환은 Controller의 역할 - const returnString = []; - if (ballCnt > 0) returnString.push(`${ballCnt}볼`); - if (strikeCnt > 0) returnString.push(`${strikeCnt}스트라이크`); - if (returnString.length === 0) { + // 문자열로 반환 + const resultString = []; + if (ballCnt > 0) resultString.push(`${ballCnt}볼`); + if (strikeCnt > 0) resultString.push(`${strikeCnt}스트라이크`); + if (resultString.length === 0) { return "낫싱"; } else { - return returnString.join(" "); - } */ - - // js에서는 return A,B 형태 사용 불가 - , 마지막 값만 평가 -> 객체로 묶어줘야 함. - return { strike: strikeCnt, ball: ballCnt }; + return resultString.join(" "); + } }; // 2. 정답값 생성 (랜덤) diff --git a/src/Controller.js b/src/Controller.js index aeeadf0..1af4e1c 100644 --- a/src/Controller.js +++ b/src/Controller.js @@ -37,23 +37,8 @@ view.bindSubmitEvent(() => { const userNumbers = userInputValue.split("").map(Number); // 통과 시 게임 실행 - // 객체로 결과 받아옴 - const { strike, ball } = game.play(computerNumbers, userNumbers); - - // 받아온 숫자 데이터 기반 텍스트 조립 - const returnStringArr = []; - if (ball > 0) returnStringArr.push(`${ball}볼`); - if (strike > 0) returnStringArr.push(`${strike}스트라이크`); - - // 최종 문자열 - // 값을 변경해야하므로 let으로 선언해야 함 - let finalResultString = ""; - if (returnStringArr.length === 0) { - finalResultString = "낫싱"; - } else { - finalResultString = returnStringArr.join(" "); - } + const resultString = game.play(computerNumbers, userNumbers); // 반환 결과 출력 - view.renderResult(finalResultString); + view.renderResult(resultString); }); From 0b1389028401ca7f4fd536002b8564dad278c610 Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Fri, 29 May 2026 15:26:36 +0900 Subject: [PATCH 26/32] =?UTF-8?q?refactor:=20=EC=A0=95=EB=8B=B5=20?= =?UTF-8?q?=ED=8C=90=EB=B3=84=20=EB=A1=9C=EC=A7=81=20MVC=20=ED=8C=A8?= =?UTF-8?q?=ED=84=B4=20=EC=A0=95=ED=99=95=ED=9E=88=20=EB=B6=84=EB=A6=AC=20?= =?UTF-8?q?=EB=B0=8F=20=EA=B2=B0=ED=95=A9=EB=8F=84=20=EB=82=AE=EC=B6=94?= =?UTF-8?q?=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BaseballGame.js | 8 ++++++++ src/Controller.js | 10 +++++++--- src/View.js | 15 ++++++++------- 3 files changed, 23 insertions(+), 10 deletions(-) diff --git a/src/BaseballGame.js b/src/BaseballGame.js index 28627f4..a553166 100644 --- a/src/BaseballGame.js +++ b/src/BaseballGame.js @@ -58,4 +58,12 @@ export default function BaseballGame() { // 3) 중복 제거 (Set) return new Set(userInputValue.split("")).size === DIGIT_COUNT; }; + + // 4. 게임 종료 판단 + this.isFinished = function (computerInputNumbers, userInputNumbers) { + // 두 배열의 모든 요소와 인덱스가 일치하는지 데이터로만 판별 + return computerInputNumbers.every( + (num, index) => num === userInputNumbers[index], + ); + }; } diff --git a/src/Controller.js b/src/Controller.js index 1af4e1c..6861d30 100644 --- a/src/Controller.js +++ b/src/Controller.js @@ -36,9 +36,13 @@ view.bindSubmitEvent(() => { // 숫자 배열로 바꿔주기 (입력값 형식 가공) const userNumbers = userInputValue.split("").map(Number); - // 통과 시 게임 실행 + // 통과 시 게임 실행 -> 결과값 받아오기 const resultString = game.play(computerNumbers, userNumbers); - // 반환 결과 출력 - view.renderResult(resultString); + // 결과 출력 + if (game.isFinished(computerNumbers, userNumbers)) { + view.renderSuccess; + } else { + view.renderResult(resultString); + } }); diff --git a/src/View.js b/src/View.js index 2665983..f02cb20 100644 --- a/src/View.js +++ b/src/View.js @@ -20,17 +20,18 @@ export default function View() { this.$userInputNum.focus(); // 커서 깜빡임 }; - // 컨트롤러가 넘겨준 결과(3스트라이크)를 화면에 그리기 + // 정답이 아닐 경우 this.renderResult = function (resultString) { - if (resultString === "3스트라이크") { - this.$resultDiv.innerHTML = ` + this.$resultDiv.innerHTML = resultString; + }; + + // 정답일 경우 + this.renderSuccess = function () { + this.$resultDiv.innerHTML = `

정답!

게임을 새로 시작하시겠어요?

`; - this.$restartButton.hidden = false; - } else { - this.$resultDiv.innerHTML = resultString; - } + this.$restartButton.hidden = false; }; // 초기화 화면 세팅 (정답값 받아오는 건 컨트롤러 역할) From 51376201a2ab0297ff85a1250ffba6120a823673 Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Fri, 29 May 2026 15:33:26 +0900 Subject: [PATCH 27/32] =?UTF-8?q?fix:=20=EC=84=B1=EA=B3=B5=20=EB=A9=94?= =?UTF-8?q?=EC=84=9C=EB=93=9C=20=ED=98=B8=EC=B6=9C=20=EC=95=88=ED=95=A8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/Controller.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Controller.js b/src/Controller.js index 6861d30..253162c 100644 --- a/src/Controller.js +++ b/src/Controller.js @@ -41,7 +41,7 @@ view.bindSubmitEvent(() => { // 결과 출력 if (game.isFinished(computerNumbers, userNumbers)) { - view.renderSuccess; + view.renderSuccess(); } else { view.renderResult(resultString); } From 71475f1ac431d036ef99a22adf640e594dcb1478 Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Fri, 29 May 2026 15:45:36 +0900 Subject: [PATCH 28/32] =?UTF-8?q?refactor:=20=EB=B3=80=EC=88=98=EB=AA=85?= =?UTF-8?q?=EC=9D=84=20=EB=8D=94=20=EC=A7=81=EA=B4=80=EC=A0=81=EC=9C=BC?= =?UTF-8?q?=EB=A1=9C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/BaseballGame.js | 24 +++++++++++------------- src/Controller.js | 8 ++++---- src/View.js | 10 +++++----- 3 files changed, 20 insertions(+), 22 deletions(-) diff --git a/src/BaseballGame.js b/src/BaseballGame.js index a553166..6e5a092 100644 --- a/src/BaseballGame.js +++ b/src/BaseballGame.js @@ -6,14 +6,14 @@ export const ERROR_MESSAGE = "1~9 사이 서로 다른 숫자 3개를 입력하 export default function BaseballGame() { // 1. 스트라이크, 볼, 낫싱 판별 - this.play = function (computerInputNumbers, userInputNumbers) { + this.play = function (computerNumbers, userNumbers) { let strikeCnt = 0; let ballCnt = 0; for (let i = 0; i < DIGIT_COUNT; i++) { - if (userInputNumbers[i] === computerInputNumbers[i]) { + if (userNumbers[i] === computerNumbers[i]) { strikeCnt++; - } else if (computerInputNumbers.includes(userInputNumbers[i])) { + } else if (computerNumbers.includes(userNumbers[i])) { ballCnt++; } } @@ -31,19 +31,19 @@ export default function BaseballGame() { // 2. 정답값 생성 (랜덤) this.getResultNum = function () { - const resultNumArr = []; + const resultNumbers = []; - while (resultNumArr.length < DIGIT_COUNT) { + while (resultNumbers.length < DIGIT_COUNT) { const randomNum = MissionUtils.Random.pickNumberInRange(MIN_NUM, MAX_NUM); - if (resultNumArr.includes(randomNum) === false) { - resultNumArr.push(randomNum); + if (resultNumbers.includes(randomNum) === false) { + resultNumbers.push(randomNum); } } - return resultNumArr; + return resultNumbers; }; // 3. 유효값 판별 (userInputValue 타입: 문자열) - this.isVaild = function (userInputValue) { + this.isValid = function (userInputValue) { // 1) 길이 검사 if (userInputValue.length !== DIGIT_COUNT) { return false; @@ -60,10 +60,8 @@ export default function BaseballGame() { }; // 4. 게임 종료 판단 - this.isFinished = function (computerInputNumbers, userInputNumbers) { + this.isFinished = function (computerNumbers, userNumbers) { // 두 배열의 모든 요소와 인덱스가 일치하는지 데이터로만 판별 - return computerInputNumbers.every( - (num, index) => num === userInputNumbers[index], - ); + return computerNumbers.every((num, index) => num === userNumbers[index]); }; } diff --git a/src/Controller.js b/src/Controller.js index 253162c..6dd13a0 100644 --- a/src/Controller.js +++ b/src/Controller.js @@ -10,17 +10,17 @@ const view = new View(); let computerNumbers = []; // 초기화 함수 -function initGame() { +function startGame() { // 정답 교체 computerNumbers = game.getResultNum(); view.resetGame(); } // 처음 시작 할 때 한 번 실행 -initGame(); +startGame(); // 재시작 버튼 클릭 이벤트 리스너 -view.bindResetEvent(initGame); +view.bindResetEvent(startGame); // 확인 버튼 클릭 이벤트 리스너 view.bindSubmitEvent(() => { @@ -28,7 +28,7 @@ view.bindSubmitEvent(() => { const userInputValue = view.getInputValue(); // 유효값 검사 후 에러처리 - if (game.isVaild(userInputValue) === false) { + if (game.isValid(userInputValue) === false) { view.renderError(ERROR_MESSAGE); return; } diff --git a/src/View.js b/src/View.js index f02cb20..75aadea 100644 --- a/src/View.js +++ b/src/View.js @@ -1,7 +1,7 @@ export default function View() { // 이벤트가 필요한 HTML 요소(DOM) 정의 // 입력창, 확인 버튼, 결과를 보여줄 공간 - this.$userInputNum = document.querySelector("#user-input"); + this.$userInput = document.querySelector("#user-input"); this.$submitButton = document.querySelector("#submit"); this.$resultDiv = document.querySelector("#result"); this.$restartButton = document.querySelector("#game-restart-button"); @@ -10,14 +10,14 @@ export default function View() { this.getInputValue = function () { // 데이터 가공은 컨트롤러가 함. // 내부 변수가 아니기 때문에 여기서도 this. 필요 - return this.$userInputNum.value; + return this.$userInput.value; }; // 에러 처리 - alert() this.renderError = function (errMessage) { alert(errMessage); - this.$userInputNum.value = ""; // 입력창 비우기 - this.$userInputNum.focus(); // 커서 깜빡임 + this.$userInput.value = ""; // 입력창 비우기 + this.$userInput.focus(); // 커서 깜빡임 }; // 정답이 아닐 경우 @@ -36,7 +36,7 @@ export default function View() { // 초기화 화면 세팅 (정답값 받아오는 건 컨트롤러 역할) this.resetGame = function () { - this.$userInputNum.value = ""; + this.$userInput.value = ""; this.$resultDiv.innerHTML = ""; this.$restartButton.hidden = true; }; From 0027e731f0b23d08ca27cda815d2e669dd2279b0 Mon Sep 17 00:00:00 2001 From: Yeryeong Kang Date: Fri, 29 May 2026 17:27:33 +0900 Subject: [PATCH 29/32] =?UTF-8?q?docs:=20README=20=ED=98=95=EC=8B=9D=20?= =?UTF-8?q?=ED=86=B5=EC=9D=BC=20=EB=B0=8F=20=EB=82=B4=EC=9A=A9=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 313 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 178 insertions(+), 135 deletions(-) diff --git a/README.md b/README.md index e43216d..95eedd2 100644 --- a/README.md +++ b/README.md @@ -1,255 +1,298 @@ -# 숫자 야구 게임 +# ⚾ 숫자 야구 게임 미션 -## 개인 목표 및 목표 달성을 위한 행동 가이드 +## 🎯 개인 목표 및 목표 달성을 위한 행동 가이드 -### 미션 전체 공통 목표 +**1. HTML, CSS, JavaScript 기본 문법 적용 및 체득** -- 로직을 이해하고, AI의 도움 없이도 스스로 문제를 해결한다. -- 문제를 해결하는 여러 가지 방안을 고민해본 후 차이점과 장단점을 비교한다. -- 몰랐던 개념이나 새롭게 알게 된 부분을 더 깊게 찾아보고 정리한다. -- AI와 스터디를 활용해 리팩토링한다. +미션 요구사항을 직접 구현하면서 바닐라 JS의 DOM 조작, 이벤트 처리, 모듈 시스템 등 기본 문법을 코드로 체득한다. -### 이번 미션 목표 +**2. MVC 패턴 이해 및 적용** -- HTML, CSS, JavaScript의 기본 문법을 알고, 미션에 적용해 체득한다. -- 몰랐던 부분, 헷갈리는 부분, 새롭게 알게 된 사실을 파악하여 확실하게 짚고 넘어간다. -- 1주차 미션인 기능 구현과 2주차 미션인 MVC 패턴 적용으로 나누어 README를 작성하고, 코드는 기능 및 순서별로 커밋을 통해 기록을 남긴다. +Model, View, Controller의 역할을 명확히 구분하여 설계하고, 각 레이어가 서로의 구현 세부사항을 알지 못하도록 의존 관계를 관리한다. + +**3. 몰랐던 개념과 새롭게 알게 된 사실 정리** + +헷갈리거나 처음 접한 개념은 단순히 넘어가지 않고 이유를 찾아 정리한다. + +**4. AI와 스터디를 활용한 리팩토링** + +구현 이후 스터디 과정에서 코드를 다시 보며 개선점을 찾고 리팩토링한다. 또한 AI 에이전트를 활용하여 과거 코드, 나의 코드, 팀원의 코드를 비교 분석하고 그 결과를 정리한다. --- -## 기능 구현 목록 +## 📝 기능 구현 목록 -### 1주차 미션: 숫자 야구 게임을 바닐라 JS로 기능 구현하기 +2주차 미션에서 새롭게 추가한 기능은 없으므로 기능 목록은 1주차 기준으로 작성한다. -2주차 미션에서 새롭게 추가한 기능은 없으므로 해당 파트는 1주차만 기록한다. +**1. 랜덤 정답 생성** -
-공통 요구사항 +`MissionUtils.Random.pickNumberInRange(1, 9)`를 세 번 호출하여 한 자리씩 확정하는 방식으로 1~9 사이의 서로 다른 임의의 수 3개를 생성한다. -1. 순수 Vanilla JS로만 구현할 것 -2. 인덴트 depth가 2를 넘지 않도록 구현할 것 -3. 함수가 한 가지 일만 하도록 최대한 작게 만들 것 -4. 변수 선언 시 `var`를 사용하지 않을 것 -5. 함수 길이가 15라인을 넘어가지 않을 것 +**2. 사용자 입력 및 유효성 검증** -
+- 사용자로부터 3자리 숫자를 입력받는다. +- 입력값이 유효하지 않은 경우 `alert`로 에러 메시지를 표시하고 다시 입력할 수 있게 한다. + - 유효하지 않은 값: 1~9 사이의 숫자로 이루어지지 않은 경우, 중복이 있는 경우, 3자리가 아닌 경우 -
-기능 요구사항 +**3. 결과 판별 및 출력** -- 1~9 중복 없는 3자리 수를 맞추는 게임 -- 컴퓨터가 1~9의 서로 다른 랜덤한 수 3개를 선택 - - 랜덤 값은 반드시 `Random.pickNumberInRange`를 사용해 구현 - - `MissionUtils` 라이브러리는 전역 객체에 추가되어 있으므로 `import` 하지 않아도 됨 - - 랜덤값 세 자리는 랜덤 함수를 세 번 호출해서 한 자리 수씩 확정 -- 결과값 규칙 +- 컴퓨터 숫자와 사용자 입력값을 비교하여 볼/스트라이크/낫싱을 판별하고 결과를 출력한다. - 수 O, 자리 O : 스트라이크 - 수 O, 자리 X : 볼 - 모든 수 X : 낫싱 -- 사용자가 값을 입력하면 결과값 출력 - - 정답이면 재시작 버튼 노출 -- 사용자가 잘못된 값을 입력한 경우 `alert`로 에러 메시지 표시 후 재입력 +- 스트라이크와 볼이 함께 있는 경우 볼을 먼저 출력한다. -
+**4. 게임 종료 및 재시작** -
-프로그래밍 요구사항 +- 정답(3스트라이크)인 경우 재시작 버튼을 화면에 노출한다. +- 재시작 버튼 클릭 시 새로운 정답을 생성하고 게임을 처음부터 다시 시작한다. -- `play(컴퓨터_랜덤값, 유저_입력값)` 메서드를 만들어서 게임 진행 -- `play` 메서드는 `String`으로 결과값 반환 - - 스트라이크와 볼이 함께 있는 경우 볼 먼저 출력 +**5. 코드 품질 요구사항** -
+- 순수 Vanilla JS로만 구현한다. +- `var`를 사용하지 않는다. +- 함수 길이는 15라인을 넘지 않도록 한다. +- 인덴트 depth는 2를 넘지 않도록 한다. +- 함수가 한 가지 일만 하도록 최대한 작게 만든다. -### 구현 기능 목록 +--- -**1. 확인 버튼 클릭 시** +## 📚 학습 내용 -- 유효성 검사 후 에러 처리 (alert 표시 및 입력창 초기화) -- 결과값 출력 -- 정답(3스트라이크) 시 재시작 버튼 노출 +**1. 생성자 함수와 객체 (Constructor Function)** -**2. 랜덤 정답 생성** +`function BaseballGame() { this.play = function(...) {} }` 형태의 생성자 함수는 `new` 키워드로 호출하면 객체를 생성한다. 단순 함수와 다르게 내부 상태(데이터)와 해당 데이터를 조작하는 메서드를 함께 묶어 캡슐화할 수 있다. -- `MissionUtils.Random.pickNumberInRange(1, 9)`를 세 번 호출하여 한 자리씩 확정 -- 중복 없는 3자리 배열 반환 +> **단점 (메모리 낭비):** `new` 키워드로 객체를 생성할 때마다 내부의 함수가 메모리에 중복하여 생성된다. ES6 클래스 문법을 사용하면 메서드가 프로토타입 공용 공간에 단 한 번만 저장되어 모든 인스턴스가 공유하므로 메모리를 더 효율적으로 관리할 수 있다. -**3. 결과 판별 (`play` 메서드)** +```javascript +// 생성자 함수 방식 +function BaseballGame() { + this.play = function (computerNumbers, userInputNumbers) { ... }; +} -- 스트라이크/볼/낫싱 판별 후 객체로 반환 +// ES6 클래스 방식 (프로토타입 활용) +class BaseballGame { + play(computerNumbers, userInputNumbers) { ... } +} +``` -**4. 유효성 검사 (`isValid` 메서드)** +**2. DOM 조작** -- 길이, 중복, 숫자 범위(1~9) 검사 +바닐라 JS에서는 `document.querySelector`로 HTML 요소를 직접 가져와 이벤트와 상태를 수동으로 연결해야 한다. CSS 선택자 규칙에 따라 id는 `#`, class는 `.`, 태그 이름은 그대로 사용한다. -**5. 재시작 버튼 클릭 시** +`innerHTML`에 HTML 문자열을 통째로 덮어씌우는 방식으로 화면을 갱신할 수 있으며, `input` 태그의 값은 `.value`로 접근한다. -- 정답 새로 생성, 결과창 초기화, 재시작 버튼 hidden 처리 +**3. `innerHTML` 동적 생성 vs `hidden` 속성** ---- +요소를 `innerHTML`로 동적 생성하면, JS가 처음 실행되는 시점에는 DOM에 요소 자체가 존재하지 않아 이벤트 리스너를 미리 등록할 수 없다는 문제가 생긴다. HTML에 미리 `hidden` 속성을 부여해두거나 CSS `display: none`을 적용하면 DOM에는 존재하면서 화면에만 보이지 않으므로, 이벤트 리스너를 초기에 등록해두고 필요한 시점에 `hidden = false`로 전환하는 방식이 더 안정적이다. -## 학습 내용 +**4. `event.preventDefault()`** -### 1주차 미션: 숫자 야구 게임을 바닐라 JS로 기능 구현하기 +`` 안에 있는 `