From cd6e987f4aa525ba303e00b409ca3f467cf45533 Mon Sep 17 00:00:00 2001 From: KAI Date: Thu, 23 Oct 2025 00:58:04 +0900 Subject: [PATCH 1/5] =?UTF-8?q?docs:=20README.md=EC=97=90=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84=ED=95=A0=20=EA=B8=B0=EB=8A=A5=20=EB=AA=A9=EB=A1=9D=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 자동차 경주 미션 요구사항에 따라, 구현을 시작하기 전 README.md 파일에 기능 목록을 체크리스트 형식으로 정리합니다. --- README.md | 33 ++++++++++++++++++++++++++++++++- 1 file changed, 32 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e078fd41..8158264c 100644 --- a/README.md +++ b/README.md @@ -1 +1,32 @@ -# javascript-racingcar-precourse +# 🚗 + +## 자동차 경주 기능 구현 목록 + +### 1. 기본 입출력 +- 자동차 이름 입력용 안내문 출력 +- 자동차 이름 입력 받기 +- 시도 횟수 입력용 안내문 출력 +- 시도할 횟수 입력 받기 + +### 2. 데이터 처리 및 validation +- 자동차 이름 분리(,) / 배열 생성 +- 자동차 이름 validation (5자 이하) +- 시도 횟수 validation (NaN) + +### 3. 수행 로직 +- 자동차 객체 생성 (이름, 포지션) +- 라운드 진행 로직 구현 (시도 횟수 만큼 반복) + - 전진/멈춤 로직 구현 + - mission-utils 활용 무작위 값 생성 + - 4 이상일 경우 전진 + +### 4. 출력 +- Console.print 사용하여 라운드별 실행 결과 출력 +- 자동차 객체 중 가장 높은 전진 횟수를 가진 객체 선정 (공동 우승 가능) +- 우승자 출력 + +### 5. 실행 +- Jest 활용 테스트 코드 진행 +- 예외 처리 +- 오류 문제 해결 + From 2d00a4f2735c65b761c23983afa62455df9b8c48 Mon Sep 17 00:00:00 2001 From: KAI Date: Sat, 25 Oct 2025 17:33:07 +0900 Subject: [PATCH 2/5] =?UTF-8?q?feat(APP):=20=EA=B8=B0=EB=B3=B8=20=EC=9E=85?= =?UTF-8?q?=EC=B6=9C=EB=A0=A5=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - getCarNames(),getTryCount() 메서드 추가 - run() 메서드에서 위 두 메서드를 await로 호출하여 입력값을 변수에 저장 --- package-lock.json | 2 ++ src/App.js | 22 +++++++++++++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/package-lock.json b/package-lock.json index 7159bf0c..e08a7f1b 100644 --- a/package-lock.json +++ b/package-lock.json @@ -52,6 +52,7 @@ "integrity": "sha512-2BCOP7TN8M+gVDj7/ht3hsaO/B/n5oDbiAyyvnRlNOs+u1o+JWNYTQrmpuNp1/Wq2gcFrI01JAW+paEKDMx/CA==", "dev": true, "license": "MIT", + "peer": true, "dependencies": { "@babel/code-frame": "^7.27.1", "@babel/generator": "^7.28.3", @@ -2969,6 +2970,7 @@ } ], "license": "MIT", + "peer": true, "dependencies": { "caniuse-lite": "^1.0.30001669", "electron-to-chromium": "^1.5.41", diff --git a/src/App.js b/src/App.js index 091aa0a5..cba26e83 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,25 @@ +import * as MissionUtils from '@woowacourse/mission-utils'; + class App { - async run() {} + async run() { + const carNamesInput = await this.getCarNames(); + + const tryCountInput = await this.getTryCount(); + } + + async getCarNames() { + const input = await MissionUtils.Console.readLineAsync( + "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)" + ); + return input; + } + + async getTryCount() { + const input = await MissionUtils.Console.readLineAsync( + "시도할 회수는 몇회인가요?" + ); + return input; + } } export default App; From 32ca94280d287281e03603cbae6c0e80a6effeb1 Mon Sep 17 00:00:00 2001 From: KAI Date: Mon, 27 Oct 2025 13:03:59 +0900 Subject: [PATCH 3/5] =?UTF-8?q?Feat[App]=20:=20=EC=9E=90=EB=8F=99=EC=B0=A8?= =?UTF-8?q?=20=EC=9D=B4=EB=A6=84=20=EB=B0=8F=20=EC=8B=9C=EB=8F=84=20?= =?UTF-8?q?=ED=9A=9F=EC=88=98=20=EC=9C=A0=ED=9A=A8=EC=84=B1=20=EA=B2=80?= =?UTF-8?q?=EC=82=AC=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 자동차 이름 validation 로직 추가 - 시도 횟수 validation 로직 추가 - 전역 에러 핸들러 추가 --- src/App.js | 45 +++++++++++++++++++++++++++++++++++++++++++++ src/index.js | 14 ++++++++++++-- 2 files changed, 57 insertions(+), 2 deletions(-) diff --git a/src/App.js b/src/App.js index cba26e83..5e9ff9c1 100644 --- a/src/App.js +++ b/src/App.js @@ -2,9 +2,19 @@ import * as MissionUtils from '@woowacourse/mission-utils'; class App { async run() { + //자동차 이름들이 5자 이하 인지 검증 + // 자동차 이름 쉼표로 분리해서 배열로 만듬 + // 시도 횟수가 유효한 숫자인지 확인 + const carNamesInput = await this.getCarNames(); + // 자동차 이름 유효성 검사 및 파싱 + const carNames = this.parseAndValidateCarNames(carNamesInput) + const tryCountInput = await this.getTryCount(); + + // 시도 횟수 유효성 검사 및 파싱 + const tryCount = this.parseAndValidateTryCount(tryCountInput); } async getCarNames() { @@ -20,6 +30,41 @@ class App { ); return input; } + + parseAndValidateCarNames(input) { + //공백 처리를 포함한 분리 + const names = input.split(",").map((name) => name.trim()); + + //5자 이상 이름 + if (names.some((name) => name.length > 5)) { + throw new Error("[ERROR] 자동차 이름은 5자 이하만 가능합니다."); + } + + //이름이 공백인 경우 + if (names.some((name) => name.length === 0)) { + throw new Error("[ERROR] 자동차 이름은 공백일 수 없습니다."); + } + + //이름 중복 + const uniqueNames = new Set(names); + if (uniqueNames.size !== names.length) { + throw new Error("[ERROR] 자동차 이름은 중복될 수 없습니다."); + } + + return names; + } + + parseAndValidateTryCount(input) { + const count = Number(input); + + if (isNaN(count)) { + throw new Error("[ERROR] 시도 횟수는 숫자여야 합니다."); + } + + return count; + + } + } export default App; diff --git a/src/index.js b/src/index.js index 02a1d389..fcc548cd 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,14 @@ +import { MissionUtils } from "@woowacourse/mission-utils"; import App from "./App.js"; -const app = new App(); -await app.run(); +class Main { + async run() { + const app = new App(); + await app.run(); + } catch (error) { + MissionUtils.Console.print(error.message); + } +} + +const main = new Main(); +main.run(); \ No newline at end of file From e9c354e04256e8d700abc9cea2f54e1afc76b450 Mon Sep 17 00:00:00 2001 From: KAI Date: Mon, 27 Oct 2025 14:44:11 +0900 Subject: [PATCH 4/5] =?UTF-8?q?Feat[App,=20Car]=20:=20=EC=88=98=ED=96=89?= =?UTF-8?q?=20=EB=A1=9C=EC=A7=81=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 자동차 객체 생성 - 라운드 진행 로직 구현 (출력 X) --- src/App.js | 20 +++++++++++++------- src/Car.js | 32 ++++++++++++++++++++++++++++++++ 2 files changed, 45 insertions(+), 7 deletions(-) create mode 100644 src/Car.js diff --git a/src/App.js b/src/App.js index 5e9ff9c1..37907c2a 100644 --- a/src/App.js +++ b/src/App.js @@ -1,20 +1,19 @@ import * as MissionUtils from '@woowacourse/mission-utils'; +import Car from './Car.js'; + class App { async run() { - //자동차 이름들이 5자 이하 인지 검증 - // 자동차 이름 쉼표로 분리해서 배열로 만듬 - // 시도 횟수가 유효한 숫자인지 확인 const carNamesInput = await this.getCarNames(); - - // 자동차 이름 유효성 검사 및 파싱 const carNames = this.parseAndValidateCarNames(carNamesInput) const tryCountInput = await this.getTryCount(); - - // 시도 횟수 유효성 검사 및 파싱 const tryCount = this.parseAndValidateTryCount(tryCountInput); + + const cars = carNames.map((name) => new Car(name)); + this.#runRace(cars, tryCount); + } async getCarNames() { @@ -65,6 +64,13 @@ class App { } + #runRace(cars, tryCount) { + MissionUtils.Console.print("\n실행 결과"); + + for (let i = 0; i < tryCount; i++) { + cars.move(); + } + } } export default App; diff --git a/src/Car.js b/src/Car.js new file mode 100644 index 00000000..0dd1a11b --- /dev/null +++ b/src/Car.js @@ -0,0 +1,32 @@ +//constructor(#name)으로 이름을 받고 position = 0 으로 초기화 +//App에서 car.move로 명령 받으면 Car에서 스스로 랜덤 값 뽑아서 position 증가 + +import { MissionUtils } from "@woowacourse/mission-utils"; + +class Car { + #name; + #position; + + constructor(name) { + this.#name = name; + this.#position = 0; + } + + move() { + const randomNumber = MissionUtils.Random.pickNumberInRange(0,9); + + if (randomNumber >= 4) { + this.#position += 1; + } + } + + getName() { + return this.#name; + } + + getPosition() { + return this.#position; + } +} + +export default Car; \ No newline at end of file From 76a3c857abe0fe5a1e8309ba9d9a9c6749f213e7 Mon Sep 17 00:00:00 2001 From: KAI Date: Tue, 28 Oct 2025 08:39:22 +0900 Subject: [PATCH 5/5] =?UTF-8?q?Feat[App]=20:=20=EA=B2=8C=EC=9E=84=20?= =?UTF-8?q?=EC=A7=84=ED=96=89=20=EB=A1=9C=EC=A7=81=20=EB=B0=8F=20=EA=B2=B0?= =?UTF-8?q?=EA=B3=BC=20=EC=B6=9C=EB=A0=A5=20=EA=B8=B0=EB=8A=A5=20=EA=B5=AC?= =?UTF-8?q?=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 시도 횟수 만큼 라운드 수 반복 진행 내용 출력 - 최종 결과 내용 출력 - 기능 테스트 정상 작동 확인 --- src/App.js | 48 +++++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 43 insertions(+), 5 deletions(-) diff --git a/src/App.js b/src/App.js index 37907c2a..50c7f5e6 100644 --- a/src/App.js +++ b/src/App.js @@ -12,7 +12,11 @@ class App { const tryCount = this.parseAndValidateTryCount(tryCountInput); const cars = carNames.map((name) => new Car(name)); + this.#runRace(cars, tryCount); + + const winnerNames = this.#findWinners(cars); + this.#printWinners(winnerNames); } @@ -31,20 +35,20 @@ class App { } parseAndValidateCarNames(input) { - //공백 처리를 포함한 분리 + const names = input.split(",").map((name) => name.trim()); - //5자 이상 이름 + if (names.some((name) => name.length > 5)) { throw new Error("[ERROR] 자동차 이름은 5자 이하만 가능합니다."); } - //이름이 공백인 경우 + if (names.some((name) => name.length === 0)) { throw new Error("[ERROR] 자동차 이름은 공백일 수 없습니다."); } - //이름 중복 + const uniqueNames = new Set(names); if (uniqueNames.size !== names.length) { throw new Error("[ERROR] 자동차 이름은 중복될 수 없습니다."); @@ -68,9 +72,43 @@ class App { MissionUtils.Console.print("\n실행 결과"); for (let i = 0; i < tryCount; i++) { - cars.move(); + + for (const car of cars) { + car.move(); + } + + + this.#printRoundResult(cars); + + MissionUtils.Console.print(""); } } + + #printRoundResult(cars) { + cars.forEach((car) => { + const positionsString = '-'.repeat(car.getPosition()); + + MissionUtils.Console.print( + `${car.getName()} : ${positionsString}` + ); + }); + }; + + #findWinners(cars) { + const positions = cars.map((car) => car.getPosition()); + const maxPosition = Math.max(...positions); + const winners = cars.filter( + (car) => car.getPosition() === maxPosition + ); + return winners.map((winner) => winner.getName()); + } + + #printWinners(winnerNames) { + const nameString = winnerNames.join(', '); + MissionUtils.Console.print( + `최종 우승자 : ${nameString}` + ); + } } export default App;