From e6e3fb108315e6e61db5278456c563e2a5bc8cce Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B0=BD=EC=A4=80?= <2018110021@dongguk.edu> Date: Mon, 28 Oct 2024 21:28:47 +0900 Subject: [PATCH 01/10] =?UTF-8?q?docs:=20=EC=9A=94=EA=B5=AC=20=EC=82=AC?= =?UTF-8?q?=ED=95=AD,=20=EA=B5=AC=ED=98=84=20=EA=B8=B0=EB=8A=A5=20?= =?UTF-8?q?=EB=AA=A9=EB=A1=9D=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 93 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 93 insertions(+) diff --git a/README.md b/README.md index e078fd41f..172367681 100644 --- a/README.md +++ b/README.md @@ -1 +1,94 @@ # javascript-racingcar-precourse + +## 과제 진행 요구 사항 + +- 미션은 문자열 덧셈 계산기 저장소를 포크하고 클론하는 것으로 시작한다. +- 기능을 구현하기 전 `README.md`에 구현할 기능 목록을 정리해 추가한다. +- Git의 커밋 단위는 앞 단계에서 README.md에 정리한 기능 목록 단위로 추가한다. + - [AngularJS Git Commit Message Conventions](https://gist.github.com/stephenparish/9941e89d80e2bc58a153)을 참고해 커밋 메시지를 작성한다. +- 자세한 과제 진행 방법은 프리코스 진행 가이드 문서를 참고한다. +## 기능 요구 사항 +초간단 자동차 경주 게임을 구현한다. + +- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다. +- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다. +- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다. +- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다. +- 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다. +- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다. +- 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다. +- 사용자가 잘못된 값을 입력할 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시킨 후 애플리케이션은 종료되어야 한다. +## 입출력 요구 사항 +### 입력 +- 경주할 자동차 이름(이름은 쉼표(,) 기준으로 구분) +```` +pobi,woni,jun +```` +- 시도할 횟수 + +``` +5 +``` +### 출력 +- 차수별 실행 결과 +``` +pobi : -- +woni : ---- +jun : --- +``` +- 단독 우승자 안내 문구 +``` +최종 우승자 : pobi +``` +- 공동 우승자 안내 문구 +``` +최종 우승자 : pobi, jun +``` + +### 실행 결과 예시 +``` +경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분) +pobi,woni,jun +시도할 횟수는 몇 회인가요? +5 + +실행 결과 +pobi : - +woni : +jun : - + +pobi : -- +woni : - +jun : -- + +pobi : --- +woni : -- +jun : --- + +pobi : ---- +woni : --- +jun : ---- + +pobi : ----- +woni : ---- +jun : ----- + +최종 우승자 : pobi, jun +``` +## 구현 기능 목록 정리 +1. 사용자 입출력 + - 경주할 자동차 이름과 시도 횟수를 입력받는다. +2. 자동차 이름 입력 검증 + - 입력된 자동차 이름이 쉼표(,)로 구분되었는지 확인한다. + - 각 자동차 이름이 5자 이하인지 검증한다. +3. 자동차 이동 조건 구현 + - 0에서 9 사이의 무작위 값을 생성하고, 4 이상일 경우 자동차가 전진하도록 설정한다. +4. 경주 실행 기능 + - 사용자 입력 시도 횟수만큼 반복하여 각 자동차의 이동을 수행하고 차수별 이동 결과를 출력한다. +5. 우승자 판별 기능 + - 모든 시도 후 가장 멀리 이동한 자동차를 우승자로 판별한다. + - 여러 우승자가 있을 경우 쉼표(,)로 구분하여 출력한다. +6. 예외 처리 + - 유효하지 않은 자동차 이름이나 시도 횟수 입력 시 “[ERROR]” 메시지를 출력하고 프로그램을 종료한다. +7. 테스트 코드 작성 + - 각 기능이 정상적으로 작동하는지 확인하기 위한 Jest 테스트 코드를 작성한다. \ No newline at end of file From 868a7d26f4497d8e04d3299b7ad6b601e2101aa7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B0=BD=EC=A4=80?= <2018110021@dongguk.edu> Date: Mon, 28 Oct 2024 21:41:40 +0900 Subject: [PATCH 02/10] =?UTF-8?q?feat(App):=20=EC=82=AC=EC=9A=A9=EC=9E=90?= =?UTF-8?q?=20=EC=9E=85=EC=B6=9C=EB=A0=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/App.js b/src/App.js index 091aa0a5d..6a82e3f76 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,20 @@ +import { Console, Random } from "@woowacourse/mission-utils"; + class App { - async run() {} + async run() { + try { + let carNameInput = await Console.readLineAsync(`경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)\n`); + let carNames = carNameInput.split(","); + let raceCountInput = await Console.readLineAsync(`시도할 횟수는 몇 회인가요?\n`); + let raceCount = parseInt(raceCountInput, 10); + + console.log("names: ",carNames); + console.log("count: ",raceCount); + } catch (error) { + Console.print(error.message); + throw error; + } + } } export default App; From 9d2b11010aa455a1d5e761f7e90d5575c4ece144 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B0=BD=EC=A4=80?= <2018110021@dongguk.edu> Date: Mon, 28 Oct 2024 21:49:24 +0900 Subject: [PATCH 03/10] =?UTF-8?q?feat(App):=20=EC=9E=90=EB=8F=99=EC=B0=A8?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EC=9E=85=EB=A0=A5=20=EA=B2=80=EC=A6=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 31 ++++++++++++++++++++++++++----- 1 file changed, 26 insertions(+), 5 deletions(-) diff --git a/src/App.js b/src/App.js index 6a82e3f76..e51854c04 100644 --- a/src/App.js +++ b/src/App.js @@ -4,17 +4,38 @@ class App { async run() { try { let carNameInput = await Console.readLineAsync(`경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)\n`); - let carNames = carNameInput.split(","); + let carNames = this.validateCarNames(carNameInput); let raceCountInput = await Console.readLineAsync(`시도할 횟수는 몇 회인가요?\n`); let raceCount = parseInt(raceCountInput, 10); - console.log("names: ",carNames); - console.log("count: ",raceCount); } catch (error) { - Console.print(error.message); - throw error; + Console.print(`[ERROR] ${error.message}`); + throw error; } } + + /** + * @author CWDll + * @describe 입력받은 문자열을 계산하는 함수 + * @parameter {carNameInput: string[]} + * @returnValue {carNames} + */ + validateCarNames(carNameInput) { + let carNames = carNameInput.split(",").map(name => name.trim()); + if (carNames.length < 2) { + throw new Error("자동차 이름은 2개 이상 입력해야 합니다."); + } + carNames.forEach(name => { + if(name.length === 0 || name.length > 5) { + throw new Error("자동차 이름은 1자 이상 5자 이하만 가능합니다."); + } + }) + + return carNames; + } } + + + export default App; From 6a047da0ef323c28090159107fbee2450a9a0e6d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B0=BD=EC=A4=80?= <2018110021@dongguk.edu> Date: Mon, 28 Oct 2024 22:07:15 +0900 Subject: [PATCH 04/10] =?UTF-8?q?feat(App):=20=EC=9E=90=EB=8F=99=EC=B0=A8?= =?UTF-8?q?=20=EC=9D=B4=EB=8F=99=20=EC=A1=B0=EA=B1=B4=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 45 +++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 43 insertions(+), 2 deletions(-) diff --git a/src/App.js b/src/App.js index e51854c04..a74dde3fc 100644 --- a/src/App.js +++ b/src/App.js @@ -8,6 +8,14 @@ class App { let raceCountInput = await Console.readLineAsync(`시도할 횟수는 몇 회인가요?\n`); let raceCount = parseInt(raceCountInput, 10); + carNames.forEach(car => { + Console.print(`${car}의 이동 결과: `); + for(let i = 0; i < raceCount; i++) { + const isMoving = this.shouldMove(); + this.carMovingCheck(car, isMoving); + } + }) + } catch (error) { Console.print(`[ERROR] ${error.message}`); throw error; @@ -16,9 +24,9 @@ class App { /** * @author CWDll - * @describe 입력받은 문자열을 계산하는 함수 + * @describe 입력받은 문자열을 검증하는 함수 * @parameter {carNameInput: string[]} - * @returnValue {carNames} + * @returnValue {carNames} - 검증된 자동차 이름 배열 */ validateCarNames(carNameInput) { let carNames = carNameInput.split(",").map(name => name.trim()); @@ -33,6 +41,39 @@ class App { return carNames; } + + /** + * @author CWDll + * @describe 난수를 생성하는 함수 + * @returnValue {number} - 0 ~ 9 사이의 난수 + */ + generateRandomNumber() { + return Random.pickNumberInRange(0, 9); + } + + /** + * @author CWDll + * @describe 난수를 생성하고, 4 이상일 경우 자동차가 전진하도록 설정하는 함수 + * @returnValue {boolean} - 전진 여부를 반환 (true: 전진, false: 멈춤) + */ + shouldMove() { + const randomNumber = this.generateRandomNumber(); + return randomNumber >= 4; + } + + /** + * @author CWDll + * @describe 선택한 자동차가 움직이는지 확인하는 함수 + */ + carMovingCheck(car, isMoving) { + if(isMoving) { + Console.print(`${car} 전진`); + } else { + Console.print(`${car} 멈춤`); + } + } + + } From 3475674d836d7efbfb3c7bae86806a86503dfa03 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B0=BD=EC=A4=80?= <2018110021@dongguk.edu> Date: Mon, 28 Oct 2024 22:13:36 +0900 Subject: [PATCH 05/10] =?UTF-8?q?feat(App):=20=EC=9E=90=EB=8F=99=EC=B0=A8?= =?UTF-8?q?=20=EC=B4=88=EA=B8=B0=20=EC=9C=84=EC=B9=98=EB=A5=BC=200?= =?UTF-8?q?=EC=9C=BC=EB=A1=9C=20=EC=84=A4=EC=A0=95=ED=95=9C=EB=8B=A4.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/App.js b/src/App.js index a74dde3fc..942a347c1 100644 --- a/src/App.js +++ b/src/App.js @@ -8,6 +8,8 @@ class App { let raceCountInput = await Console.readLineAsync(`시도할 횟수는 몇 회인가요?\n`); let raceCount = parseInt(raceCountInput, 10); + let carPositions = this.initializeCarPositions(carNames); + carNames.forEach(car => { Console.print(`${car}의 이동 결과: `); for(let i = 0; i < raceCount; i++) { @@ -64,6 +66,7 @@ class App { /** * @author CWDll * @describe 선택한 자동차가 움직이는지 확인하는 함수 + * @parameter {carName: string, isMoving: boolean} */ carMovingCheck(car, isMoving) { if(isMoving) { @@ -73,6 +76,19 @@ class App { } } + /** + * @author CWDll + * @describe 자동차 초기 위치(0)를 설정하는 함수 + * @parameter {carNames: string[]} + */ + initializeCarPositions(carNames) { + const carPositions = {}; + carNames.forEach(car => { + carPositions[car] = 0; + }); + return carPositions; + } + } From 8120d330bb44fa3a10bad5554f923ce843dbad01 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B0=BD=EC=A4=80?= <2018110021@dongguk.edu> Date: Mon, 28 Oct 2024 22:34:39 +0900 Subject: [PATCH 06/10] =?UTF-8?q?feat(App):=20=EC=9E=90=EB=8F=99=EC=B0=A8?= =?UTF-8?q?=20=EA=B2=BD=EC=A3=BC=20=EC=8B=A4=ED=96=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 39 ++++++++++++++++++++++++++++++--------- 1 file changed, 30 insertions(+), 9 deletions(-) diff --git a/src/App.js b/src/App.js index 942a347c1..92f985366 100644 --- a/src/App.js +++ b/src/App.js @@ -10,13 +10,7 @@ class App { let carPositions = this.initializeCarPositions(carNames); - carNames.forEach(car => { - Console.print(`${car}의 이동 결과: `); - for(let i = 0; i < raceCount; i++) { - const isMoving = this.shouldMove(); - this.carMovingCheck(car, isMoving); - } - }) + this.runRace(carNames, carPositions, raceCount); } catch (error) { Console.print(`[ERROR] ${error.message}`); @@ -68,8 +62,9 @@ class App { * @describe 선택한 자동차가 움직이는지 확인하는 함수 * @parameter {carName: string, isMoving: boolean} */ - carMovingCheck(car, isMoving) { - if(isMoving) { + carMovingCheck(car, isMoving, carPositions) { + if (isMoving) { + carPositions[car] += 1; Console.print(`${car} 전진`); } else { Console.print(`${car} 멈춤`); @@ -89,6 +84,32 @@ class App { return carPositions; } + /** + * @author CWDll + * @describe 자동차 경주를 실행하는 함수 + * @parameter {carNames: string[], carPositions: Object, raceCount: number} + */ + runRace(carNames, carPositions, raceCount) { + for(let i=0 ; i < raceCount; i++) { + Console.print(`\n${i+1}회 경주 결과`); + + carNames.forEach(car => { + const isMoving = this.shouldMove(); + this.carMovingCheck(car, isMoving, carPositions); + this.showCarPosition(car, carPositions[car]); + }) + } + } + + /** + * @author CWDll + * @describe 자동차 현재 위치를 출력하는 함수 + * @parameter {carName: string, position: number} + */ + showCarPosition(car, position) { + const positionMark = "-".repeat(position); + Console.print(`${car} : ${positionMark}`); + } } From 5d04d0a6df8fbb28b11fadabc5956c6246dd7840 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B0=BD=EC=A4=80?= <2018110021@dongguk.edu> Date: Mon, 28 Oct 2024 22:42:06 +0900 Subject: [PATCH 07/10] =?UTF-8?q?feat(App):=20=EA=B2=BD=EA=B8=B0=20?= =?UTF-8?q?=EC=9A=B0=EC=8A=B9=EC=9E=90=20=ED=8C=90=EB=B3=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/src/App.js b/src/App.js index 92f985366..393bbd240 100644 --- a/src/App.js +++ b/src/App.js @@ -9,9 +9,11 @@ class App { let raceCount = parseInt(raceCountInput, 10); let carPositions = this.initializeCarPositions(carNames); - this.runRace(carNames, carPositions, raceCount); + const winners = this.getWinners(carPositions); + Console.print(`\n최종 우승자: ${winners.join(", ")}`); + } catch (error) { Console.print(`[ERROR] ${error.message}`); throw error; @@ -65,9 +67,6 @@ class App { carMovingCheck(car, isMoving, carPositions) { if (isMoving) { carPositions[car] += 1; - Console.print(`${car} 전진`); - } else { - Console.print(`${car} 멈춤`); } } @@ -111,6 +110,17 @@ class App { Console.print(`${car} : ${positionMark}`); } + /** + * @author CWDll + * @describe runRace 우승자 결정하는 함수 + * @parameter {carPositions: Object} + * @returnValue {string[]} - 우승자 이름 배열 + */ + getWinners(carPositions) { + const maxPosition = Math.max(...Object.values(carPositions)); + const winners = Object.keys(carPositions).filter(car => carPositions[car] === maxPosition); + return winners; + } } From 1898cb1fea9838ba3c1fcc15b383f97107ba1cd0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B0=BD=EC=A4=80?= <2018110021@dongguk.edu> Date: Mon, 28 Oct 2024 22:52:17 +0900 Subject: [PATCH 08/10] =?UTF-8?q?fix(App):=20=EA=B8=B0=EB=8C=80=EA=B0=92?= =?UTF-8?q?=EC=97=90=20=EB=A7=9E=EC=B6=94=EC=96=B4=20=EC=B6=9C=EB=A0=A5=20?= =?UTF-8?q?=EB=AC=B8=EC=9E=90=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/src/App.js b/src/App.js index 393bbd240..66ef298b3 100644 --- a/src/App.js +++ b/src/App.js @@ -12,7 +12,7 @@ class App { this.runRace(carNames, carPositions, raceCount); const winners = this.getWinners(carPositions); - Console.print(`\n최종 우승자: ${winners.join(", ")}`); + Console.print(`\n최종 우승자 : ${winners.join(", ")}`); } catch (error) { Console.print(`[ERROR] ${error.message}`); @@ -29,11 +29,11 @@ class App { validateCarNames(carNameInput) { let carNames = carNameInput.split(",").map(name => name.trim()); if (carNames.length < 2) { - throw new Error("자동차 이름은 2개 이상 입력해야 합니다."); + throw new Error("[ERROR] 자동차 이름은 2개 이상 입력해야 합니다."); } carNames.forEach(name => { if(name.length === 0 || name.length > 5) { - throw new Error("자동차 이름은 1자 이상 5자 이하만 가능합니다."); + throw new Error("[ERROR] 자동차 이름은 1자 이상 5자 이하만 가능합니다."); } }) @@ -90,8 +90,6 @@ class App { */ runRace(carNames, carPositions, raceCount) { for(let i=0 ; i < raceCount; i++) { - Console.print(`\n${i+1}회 경주 결과`); - carNames.forEach(car => { const isMoving = this.shouldMove(); this.carMovingCheck(car, isMoving, carPositions); From 0fc853d65b05fd290c50b70b67ae1f7e607b58dc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B0=BD=EC=A4=80?= <2018110021@dongguk.edu> Date: Mon, 28 Oct 2024 22:55:12 +0900 Subject: [PATCH 09/10] =?UTF-8?q?feat(App):=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EB=AC=B8=EA=B5=AC=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/App.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/App.js b/src/App.js index 66ef298b3..4e9785962 100644 --- a/src/App.js +++ b/src/App.js @@ -5,8 +5,12 @@ class App { try { let carNameInput = await Console.readLineAsync(`경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)\n`); let carNames = this.validateCarNames(carNameInput); + let raceCountInput = await Console.readLineAsync(`시도할 횟수는 몇 회인가요?\n`); let raceCount = parseInt(raceCountInput, 10); + if (isNaN(raceCount) || raceCount <= 0) { + throw new Error("[ERROR] 시도 횟수는 1 이상의 정수를 입력해야 합니다."); + } let carPositions = this.initializeCarPositions(carNames); this.runRace(carNames, carPositions, raceCount); From 40908ef501cb089290a9630ccb4e386009c785bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EC=B0=BD=EC=A4=80?= <2018110021@dongguk.edu> Date: Mon, 28 Oct 2024 22:58:47 +0900 Subject: [PATCH 10/10] =?UTF-8?q?feat(ApplicationTest):=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=BD=94=EB=93=9C=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- __tests__/ApplicationTest.js | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/__tests__/ApplicationTest.js b/__tests__/ApplicationTest.js index 0260e7e84..67d571a1a 100644 --- a/__tests__/ApplicationTest.js +++ b/__tests__/ApplicationTest.js @@ -48,13 +48,25 @@ describe("자동차 경주", () => { test("예외 테스트", async () => { // given - const inputs = ["pobi,javaji"]; + const inputs = ["pobi,javaji"]; // 5자 초과 mockQuestions(inputs); // when const app = new App(); // then - await expect(app.run()).rejects.toThrow("[ERROR]"); + await expect(app.run()).rejects.toThrow("[ERROR] 자동차 이름은 1자 이상 5자 이하만 가능합니다."); + }); + + test("시도 횟수 예외 테스트", async () => { + // given + const inputs = ["pobi,woni", "0"]; // 시도 횟수 0회 + mockQuestions(inputs); + + // when + const app = new App(); + + // then + await expect(app.run()).rejects.toThrow("[ERROR] 시도 횟수는 1 이상의 정수를 입력해야 합니다."); }); });