From 8e0bf94d1e1ffa77e2629f743f4ccff8918cca5b Mon Sep 17 00:00:00 2001 From: yuni Date: Fri, 14 Feb 2025 03:42:46 +0900 Subject: [PATCH 01/13] =?UTF-8?q?=F0=9F=93=9D=20Update=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 53 insertions(+) diff --git a/README.md b/README.md index e078fd4..ad36387 100644 --- a/README.md +++ b/README.md @@ -1 +1,54 @@ # javascript-racingcar-precourse +# 2주차 미션 : 자동차 경주 :car: + +## Todo List +- :white_check_mark: 입력 + - 문자열 입력 기능 + - 숫자 입력 기능 + +- :white_check_mark: 전처리 + - 쉼표를 기준으로 이름을 구분 + +- :white_check_mark: 전진/멈춤할지 고르기 + - 입력받은 시도 횟수만큼 반복 + - 0-9 사이 무작위값 받기 + - 전진(4 이상) / 멈춤 (그 외) + +- :white_check_mark: 우승자 고르기 + - 전진한 횟수 비교 + +- :white_check_mark: 예외처리 + - 입력 오류 : 이름이 잘못 입력된 경우 + - 입력 오류 : 몇 번 이동할 것인지 입력하는 것이 숫자가 아닌 경우 + - 입력 처리 : 이름 양쪽 공백은 삭제한다. 즉, '지윤' = ' 지윤 ' + - 예외 처리 : 이름이 중복되는 경우, 한 사람으로 처리한다. + +- :white_check_mark: 리펙토링 + - MVC 패턴... + - 복잡한 코드 흐름 개선 + +## 코드 흐름 +1. "경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)" 출력 후 입력 대기 +2. "시도할 횟수는 몇 회인가요?" 출력 후 입력 대기 +3. 문자열 전처리 + a. ','기준으로 나누기 + b. 문자열 양 옆 공백 지우기 + + 예외처리 : 중복제거, 잘못된 입력 오류 처리 +4. 경주 시작 + a. 랜덤값으로 전진/멈춤 고르기 + b. 전진 수 저장 + c. n번 반복 +5. 실행 결과 출력 + a. 저장된 전진 수 만큼 '-'를 반복 출력 + b. n번 반복 +6. 우승자 결정 및 출력 + a. 전진 수 비교 + b. 우승자 저장 + +## MVC 패턴 + +### Main +### Model (Data) +### View (UI) +### Controller (Interface) + From 413418ce18725981a6e46051af091424061ac2e7 Mon Sep 17 00:00:00 2001 From: yuni Date: Fri, 14 Feb 2025 03:51:22 +0900 Subject: [PATCH 02/13] =?UTF-8?q?=E2=9C=A8=20Feat=20=EC=9E=85=EB=A0=A5=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=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 | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/src/App.js b/src/App.js index 091aa0a..18c8062 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,17 @@ +import { Console, Random } from "@woowacourse/mission-utils"; + +class Car { + constructor(name, count) { + this.name = name; + this.count = count; + } +} + class App { - async run() {} + async run() { + const inputName = await Console.readLineAsync("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분): "); + const inputNum = await Console.readLineAsync("시도할 횟수는 몇 회인가요?: "); + } } export default App; From bf169b914282d869a9b4c11637b7d64d54dac619 Mon Sep 17 00:00:00 2001 From: yuni Date: Fri, 14 Feb 2025 04:17:21 +0900 Subject: [PATCH 03/13] =?UTF-8?q?=E2=9C=A8=20Feat=20=EB=AC=B8=EC=9E=90?= =?UTF-8?q?=EC=97=B4=20=EC=A0=84=EC=B2=98=EB=A6=AC=20=EA=B8=B0=EB=8A=A5=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 쉼표를 기준으로 이름을 구분하는 문자열 전처리 기능을 추가하였다. --- src/App.js | 17 ++++++++++++++++- 1 file changed, 16 insertions(+), 1 deletion(-) diff --git a/src/App.js b/src/App.js index 18c8062..f0c2869 100644 --- a/src/App.js +++ b/src/App.js @@ -1,16 +1,31 @@ import { Console, Random } from "@woowacourse/mission-utils"; class Car { - constructor(name, count) { + constructor(name, count = 0) { this.name = name; this.count = count; } } +class Racing { + static AdvanceCount(Names){ + //전처리 + let tempArray = []; + let nameArray = [Names]; + for (const name of nameArray) { + tempArray = tempArray.concat(name.split(',')); + } + nameArray = tempArray; + return nameArray; + } +} + class App { + //입력 받기 async run() { const inputName = await Console.readLineAsync("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분): "); const inputNum = await Console.readLineAsync("시도할 횟수는 몇 회인가요?: "); + Console.print(Racing.AdvanceCount(inputName)); } } From 2a56c56b3e222e4f76722c66b5f1f05d91bf35f7 Mon Sep 17 00:00:00 2001 From: yuni Date: Fri, 14 Feb 2025 05:35:47 +0900 Subject: [PATCH 04/13] =?UTF-8?q?=E2=9C=A8=20Feat=20=EC=A0=84=EC=A7=84/?= =?UTF-8?q?=EB=A9=88=EC=B6=A4=20=ED=8C=90=EB=B3=84=20=EB=B0=8F=20=EC=B9=B4?= =?UTF-8?q?=EC=9A=B4=ED=8A=B8=20=EA=B8=B0=EB=8A=A5=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 | 27 +++++++++++++++++++++++---- 1 file changed, 23 insertions(+), 4 deletions(-) diff --git a/src/App.js b/src/App.js index f0c2869..37c7393 100644 --- a/src/App.js +++ b/src/App.js @@ -1,14 +1,14 @@ import { Console, Random } from "@woowacourse/mission-utils"; class Car { - constructor(name, count = 0) { + constructor(name,count = 0) { this.name = name; this.count = count; } } class Racing { - static AdvanceCount(Names){ + static AdvanceCount(Names,Num){ //전처리 let tempArray = []; let nameArray = [Names]; @@ -16,16 +16,35 @@ class Racing { tempArray = tempArray.concat(name.split(',')); } nameArray = tempArray; - return nameArray; + + //인스턴스화 + let carInstances = nameArray.map(name => new Car(name)); + + //전진 카운트 + for (let i = 0; i < Num; i++){ + Console.print(`i:${i}`); + for (let j = 0; j < carInstances.length; j++){ + const randomNum = Random.pickNumberInRange(0, 9); + const advance = ( randomNum >= 4) ? true : false; + if (advance){ + carInstances[j].count++; + Console.print(`${carInstances[j].name}:${carInstances[j].count}`); + } + } + } + return carInstances; } } + + class App { //입력 받기 async run() { const inputName = await Console.readLineAsync("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분): "); const inputNum = await Console.readLineAsync("시도할 횟수는 몇 회인가요?: "); - Console.print(Racing.AdvanceCount(inputName)); + + Console.print(Racing.AdvanceCount(inputName,inputNum)); } } From e7779bc329eaf5b81f66579ceab056d0001cd518 Mon Sep 17 00:00:00 2001 From: yuni Date: Fri, 14 Feb 2025 05:44:47 +0900 Subject: [PATCH 05/13] =?UTF-8?q?=E2=9C=A8=20Feat=20=EC=B6=9C=EB=A0=A5=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5=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 | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/src/App.js b/src/App.js index 37c7393..b04ec83 100644 --- a/src/App.js +++ b/src/App.js @@ -19,7 +19,7 @@ class Racing { //인스턴스화 let carInstances = nameArray.map(name => new Car(name)); - + //전진 카운트 for (let i = 0; i < Num; i++){ Console.print(`i:${i}`); @@ -34,17 +34,25 @@ class Racing { } return carInstances; } -} + static winner(){ + + } +} class App { - //입력 받기 async run() { + //입력 받기 const inputName = await Console.readLineAsync("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분): "); const inputNum = await Console.readLineAsync("시도할 횟수는 몇 회인가요?: "); + const cars = Racing.AdvanceCount(inputName,inputNum); - Console.print(Racing.AdvanceCount(inputName,inputNum)); + //출력 + Console.print('실행 결과'); + for (let i = 0; i < cars.length; i++){ + Console.print(`${cars[i].name} : ${'-'.repeat(cars[i].count)}`); + } } } From 160ddd344868d916ee3fd7489e37aca33fc863e6 Mon Sep 17 00:00:00 2001 From: yuni Date: Fri, 14 Feb 2025 11:28:08 +0900 Subject: [PATCH 06/13] =?UTF-8?q?=F0=9F=90=9B=20Fix=20=EC=B6=9C=EB=A0=A5?= =?UTF-8?q?=20=EA=B8=B0=EB=8A=A5=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 문제에서 제시하는 조건에 일치하도록 출력 기능을 수정하였다. --- src/App.js | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/src/App.js b/src/App.js index b04ec83..90082ae 100644 --- a/src/App.js +++ b/src/App.js @@ -22,14 +22,14 @@ class Racing { //전진 카운트 for (let i = 0; i < Num; i++){ - Console.print(`i:${i}`); + Console.print(''); for (let j = 0; j < carInstances.length; j++){ const randomNum = Random.pickNumberInRange(0, 9); const advance = ( randomNum >= 4) ? true : false; if (advance){ carInstances[j].count++; - Console.print(`${carInstances[j].name}:${carInstances[j].count}`); } + Console.print(`${carInstances[j].name} : ${'-'.repeat(carInstances[j].count)}`); } } return carInstances; @@ -46,13 +46,10 @@ class App { //입력 받기 const inputName = await Console.readLineAsync("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분): "); const inputNum = await Console.readLineAsync("시도할 횟수는 몇 회인가요?: "); - const cars = Racing.AdvanceCount(inputName,inputNum); //출력 Console.print('실행 결과'); - for (let i = 0; i < cars.length; i++){ - Console.print(`${cars[i].name} : ${'-'.repeat(cars[i].count)}`); - } + Racing.AdvanceCount(inputName,inputNum); } } From 2aa0734962404f8ddca876379ef43c846d4f2095 Mon Sep 17 00:00:00 2001 From: yuni Date: Fri, 14 Feb 2025 11:45:02 +0900 Subject: [PATCH 07/13] =?UTF-8?q?=E2=9C=A8=20Feat=20=EC=9A=B0=EC=8A=B9?= =?UTF-8?q?=EC=9E=90=20=ED=8C=90=EB=B3=84=20=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 전진 수를 비교하여 우승자 이름을 출력하는 기능을 추가하였다. --- src/App.js | 24 +++++++++++++++++------- 1 file changed, 17 insertions(+), 7 deletions(-) diff --git a/src/App.js b/src/App.js index 90082ae..b142cca 100644 --- a/src/App.js +++ b/src/App.js @@ -8,6 +8,10 @@ class Car { } class Racing { + constructor() { + this.carInstances = []; + } + static AdvanceCount(Names,Num){ //전처리 let tempArray = []; @@ -18,25 +22,30 @@ class Racing { nameArray = tempArray; //인스턴스화 - let carInstances = nameArray.map(name => new Car(name)); + this.carInstances = nameArray.map(name => new Car(name)); //전진 카운트 for (let i = 0; i < Num; i++){ Console.print(''); - for (let j = 0; j < carInstances.length; j++){ + for (let j = 0; j < this.carInstances.length; j++){ const randomNum = Random.pickNumberInRange(0, 9); const advance = ( randomNum >= 4) ? true : false; if (advance){ - carInstances[j].count++; + this.carInstances[j].count++; } - Console.print(`${carInstances[j].name} : ${'-'.repeat(carInstances[j].count)}`); + Console.print(`${this.carInstances[j].name} : ${'-'.repeat(this.carInstances[j].count)}`); } } - return carInstances; + return this.carInstances; } static winner(){ - + //가장 큰 값 찾기 + const maxValue = Math.max(...this.carInstances.map(itemm => itemm.count)); + //가장 큰 값과 같은 값을 가진 CarName 만 추출 + const maxCarNames = this.carInstances.filter(item => item.count === maxValue).map(item => item.name); + + return maxCarNames; } } @@ -50,7 +59,8 @@ class App { //출력 Console.print('실행 결과'); Racing.AdvanceCount(inputName,inputNum); + Console.print(`최종 우승자 : ${Racing.winner().join(', ')}`); } } -export default App; +export default App; \ No newline at end of file From 7dd7c239e3f4569f02903ea9087b3993b8f40c2a Mon Sep 17 00:00:00 2001 From: yuni Date: Fri, 14 Feb 2025 17:25:33 +0900 Subject: [PATCH 08/13] =?UTF-8?q?=E2=9C=A8=20Feat=20=EC=98=88=EC=99=B8=20?= =?UTF-8?q?=EC=B2=98=EB=A6=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 | 60 ++++++++++++++++++++++++++++++++++++++---------------- 1 file changed, 43 insertions(+), 17 deletions(-) diff --git a/src/App.js b/src/App.js index b142cca..6b2ae65 100644 --- a/src/App.js +++ b/src/App.js @@ -8,19 +8,32 @@ class Car { } class Racing { + constructor() { this.carInstances = []; } - static AdvanceCount(Names,Num){ - //전처리 - let tempArray = []; - let nameArray = [Names]; - for (const name of nameArray) { - tempArray = tempArray.concat(name.split(',')); - } - nameArray = tempArray; + validateCarNames(names) { + // 전처리 + let nameArray = names.split(",").map(name => name.trim()); + + // 빈 문자열 처리 + nameArray = nameArray.filter(name => name !== ""); + // 자동차 이름 중복 처리 + nameArray = Array.from(new Set(nameArray)); + + // 자동차 이름은 5자 이하 + nameArray.forEach(name => { + if (name.length > 5) { + throw new Error("[ERROR] IllegalArgumentException"); + } + }); + + return nameArray; + } + + AdvanceCount(nameArray,Num){ //인스턴스화 this.carInstances = nameArray.map(name => new Car(name)); @@ -39,7 +52,7 @@ class Racing { return this.carInstances; } - static winner(){ + winner(){ //가장 큰 값 찾기 const maxValue = Math.max(...this.carInstances.map(itemm => itemm.count)); //가장 큰 값과 같은 값을 가진 CarName 만 추출 @@ -49,17 +62,30 @@ class Racing { } } - class App { async run() { - //입력 받기 - const inputName = await Console.readLineAsync("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분): "); - const inputNum = await Console.readLineAsync("시도할 횟수는 몇 회인가요?: "); + try{ + const racing = new Racing(); + + //이름 입력받기, 예외 처리 + const inputName = await Console.readLineAsync("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분): "); + const nameArray = racing.validateCarNames(inputName); - //출력 - Console.print('실행 결과'); - Racing.AdvanceCount(inputName,inputNum); - Console.print(`최종 우승자 : ${Racing.winner().join(', ')}`); + //시도 횟수 입력받기, 예외 처리 + const inputNum = await Console.readLineAsync("시도할 횟수는 몇 회인가요?: "); + if (isNaN(inputNum) || inputNum <= 0) { + throw new Error("[ERROR] IllegalArgumentException"); + } + + //출력 + Console.print('실행 결과'); + racing.AdvanceCount(nameArray,inputNum); + Console.print(`최종 우승자 : ${racing.winner().join(', ')}`); + + } catch (error) { + Console.print(error.message); + return Promise.reject(error); + } } } From aeb091914c63c3288ecee5471978109acda2877e Mon Sep 17 00:00:00 2001 From: yuni Date: Fri, 14 Feb 2025 17:26:49 +0900 Subject: [PATCH 09/13] =?UTF-8?q?=F0=9F=93=9D=20Update=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 17 ++++++++--------- 1 file changed, 8 insertions(+), 9 deletions(-) diff --git a/README.md b/README.md index ad36387..f9c8de1 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,29 @@ # javascript-racingcar-precourse -# 2주차 미션 : 자동차 경주 :car: +# 2주차 미션 : 자동차 경주🚗 ## Todo List -- :white_check_mark: 입력 +- ✅ 입력 - 문자열 입력 기능 - 숫자 입력 기능 -- :white_check_mark: 전처리 +- ✅ 전처리 - 쉼표를 기준으로 이름을 구분 -- :white_check_mark: 전진/멈춤할지 고르기 +- ✅ 전진/멈춤할지 고르기 - 입력받은 시도 횟수만큼 반복 - 0-9 사이 무작위값 받기 - 전진(4 이상) / 멈춤 (그 외) -- :white_check_mark: 우승자 고르기 +- ✅ 우승자 고르기 - 전진한 횟수 비교 -- :white_check_mark: 예외처리 +- ✅ 예외처리 - 입력 오류 : 이름이 잘못 입력된 경우 - 입력 오류 : 몇 번 이동할 것인지 입력하는 것이 숫자가 아닌 경우 - 입력 처리 : 이름 양쪽 공백은 삭제한다. 즉, '지윤' = ' 지윤 ' - 예외 처리 : 이름이 중복되는 경우, 한 사람으로 처리한다. -- :white_check_mark: 리펙토링 +- ✅ 리펙토링 - MVC 패턴... - 복잡한 코드 흐름 개선 @@ -50,5 +50,4 @@ ### Main ### Model (Data) ### View (UI) -### Controller (Interface) - +### Controller (Interface) \ No newline at end of file From ff72716c7552ac44b55542c0939271ac561397ce Mon Sep 17 00:00:00 2001 From: yuni Date: Fri, 14 Feb 2025 17:56:22 +0900 Subject: [PATCH 10/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20=EC=A4=91?= =?UTF-8?q?=EB=B3=B5=20=EC=BD=94=EB=93=9C=20=EC=A0=9C=EA=B1=B0=20=EB=B0=8F?= =?UTF-8?q?=20=EC=BD=94=EB=93=9C=20=EA=B5=AC=EC=A1=B0=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 중복 코드를 함수로 만드는 등 코드를 간결하게 개선하였다. --- src/App.js | 79 ++++++++++++++++++++++++------------------------------ 1 file changed, 35 insertions(+), 44 deletions(-) diff --git a/src/App.js b/src/App.js index 6b2ae65..31348d8 100644 --- a/src/App.js +++ b/src/App.js @@ -1,64 +1,55 @@ import { Console, Random } from "@woowacourse/mission-utils"; class Car { - constructor(name,count = 0) { + constructor(name) { this.name = name; - this.count = count; + this.count = 0; + } + + // 전진 조건을 적용하는 메서드 + move() { + if (Random.pickNumberInRange(0, 9) >= 4) { + this.count++; + } + } + + // 자동차 상태 출력 + print() { + Console.print(`${this.name} : ${'-'.repeat(this.count)}`); } } class Racing { - constructor() { this.carInstances = []; } validateCarNames(names) { - // 전처리 - let nameArray = names.split(",").map(name => name.trim()); - - // 빈 문자열 처리 - nameArray = nameArray.filter(name => name !== ""); - - // 자동차 이름 중복 처리 - nameArray = Array.from(new Set(nameArray)); - - // 자동차 이름은 5자 이하 - nameArray.forEach(name => { - if (name.length > 5) { - throw new Error("[ERROR] IllegalArgumentException"); - } - }); - - return nameArray; + // 쉼표 기준 분리 | 공백 처리 | 5자 이하 처리 | 중복 처리 + return [...new Set(names.split(",") + .map(name => name.trim()) + .filter(name => name && name.length <= 5))]; } - AdvanceCount(nameArray,Num){ - //인스턴스화 + advanceCount(nameArray,Num){ + // 인스턴스화 this.carInstances = nameArray.map(name => new Car(name)); - //전진 카운트 + // 전진 카운트 for (let i = 0; i < Num; i++){ Console.print(''); - for (let j = 0; j < this.carInstances.length; j++){ - const randomNum = Random.pickNumberInRange(0, 9); - const advance = ( randomNum >= 4) ? true : false; - if (advance){ - this.carInstances[j].count++; - } - Console.print(`${this.carInstances[j].name} : ${'-'.repeat(this.carInstances[j].count)}`); - } + this.carInstances.forEach(car => { + car.move(); + car.print(); + }) } - return this.carInstances; } - winner(){ - //가장 큰 값 찾기 - const maxValue = Math.max(...this.carInstances.map(itemm => itemm.count)); - //가장 큰 값과 같은 값을 가진 CarName 만 추출 - const maxCarNames = this.carInstances.filter(item => item.count === maxValue).map(item => item.name); - - return maxCarNames; + getWinner(){ + // 가장 큰 값 찾기 + const maxCount = Math.max(...this.carInstances.map(car => car.count)); + // 가장 큰 값과 같은 값을 가진 CarName 만 추출 + return this.carInstances.filter(car => car.count === maxCount).map(car => car.name); } } @@ -67,20 +58,20 @@ class App { try{ const racing = new Racing(); - //이름 입력받기, 예외 처리 + // 이름 입력받기, 예외 처리 const inputName = await Console.readLineAsync("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분): "); const nameArray = racing.validateCarNames(inputName); - //시도 횟수 입력받기, 예외 처리 + // 시도 횟수 입력받기, 예외 처리 const inputNum = await Console.readLineAsync("시도할 횟수는 몇 회인가요?: "); if (isNaN(inputNum) || inputNum <= 0) { throw new Error("[ERROR] IllegalArgumentException"); } - //출력 + // 출력 Console.print('실행 결과'); - racing.AdvanceCount(nameArray,inputNum); - Console.print(`최종 우승자 : ${racing.winner().join(', ')}`); + racing.advanceCount(nameArray,inputNum); + Console.print(`최종 우승자 : ${racing.getWinner().join(', ')}`); } catch (error) { Console.print(error.message); From 67da1521f2c2d53cb3259f4953933d4b7fa8e033 Mon Sep 17 00:00:00 2001 From: yuni Date: Fri, 14 Feb 2025 21:24:44 +0900 Subject: [PATCH 11/13] =?UTF-8?q?=E2=99=BB=EF=B8=8F=20Refactor=20MVC=20?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=EC=9C=BC=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 App.js, Model.js, View.js 추가, MVC 패턴으로 수정하였다. --- src/App.js | 78 ++++++++-------------------------------------------- src/Model.js | 56 +++++++++++++++++++++++++++++++++++++ src/View.js | 30 ++++++++++++++++++++ 3 files changed, 98 insertions(+), 66 deletions(-) create mode 100644 src/Model.js create mode 100644 src/View.js diff --git a/src/App.js b/src/App.js index 31348d8..fdce6f0 100644 --- a/src/App.js +++ b/src/App.js @@ -1,83 +1,29 @@ -import { Console, Random } from "@woowacourse/mission-utils"; - -class Car { - constructor(name) { - this.name = name; - this.count = 0; - } - - // 전진 조건을 적용하는 메서드 - move() { - if (Random.pickNumberInRange(0, 9) >= 4) { - this.count++; - } - } - - // 자동차 상태 출력 - print() { - Console.print(`${this.name} : ${'-'.repeat(this.count)}`); - } -} - -class Racing { - constructor() { - this.carInstances = []; - } - - validateCarNames(names) { - // 쉼표 기준 분리 | 공백 처리 | 5자 이하 처리 | 중복 처리 - return [...new Set(names.split(",") - .map(name => name.trim()) - .filter(name => name && name.length <= 5))]; - } - - advanceCount(nameArray,Num){ - // 인스턴스화 - this.carInstances = nameArray.map(name => new Car(name)); - - // 전진 카운트 - for (let i = 0; i < Num; i++){ - Console.print(''); - this.carInstances.forEach(car => { - car.move(); - car.print(); - }) - } - } - - getWinner(){ - // 가장 큰 값 찾기 - const maxCount = Math.max(...this.carInstances.map(car => car.count)); - // 가장 큰 값과 같은 값을 가진 CarName 만 추출 - return this.carInstances.filter(car => car.count === maxCount).map(car => car.name); - } -} +import { Racing } from './Model.js'; +import View from './View.js'; class App { async run() { - try{ + try { const racing = new Racing(); - // 이름 입력받기, 예외 처리 - const inputName = await Console.readLineAsync("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분): "); + const inputName = await View.readLine("경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분): "); const nameArray = racing.validateCarNames(inputName); - // 시도 횟수 입력받기, 예외 처리 - const inputNum = await Console.readLineAsync("시도할 횟수는 몇 회인가요?: "); + const inputNum = await View.readLine("시도할 횟수는 몇 회인가요?: "); if (isNaN(inputNum) || inputNum <= 0) { throw new Error("[ERROR] IllegalArgumentException"); } - // 출력 - Console.print('실행 결과'); - racing.advanceCount(nameArray,inputNum); - Console.print(`최종 우승자 : ${racing.getWinner().join(', ')}`); - + View.print('실행 결과'); + const results = racing.advanceCount(nameArray, inputNum); + View.printResults(results); + View.printWinner(racing.getWinner()); + } catch (error) { - Console.print(error.message); + View.printError(error.message); return Promise.reject(error); } } } -export default App; \ No newline at end of file +export default App; diff --git a/src/Model.js b/src/Model.js new file mode 100644 index 0000000..67f8245 --- /dev/null +++ b/src/Model.js @@ -0,0 +1,56 @@ +import { Random } from "@woowacourse/mission-utils"; + +class Car { + constructor(name) { + this.name = name; + this.count = 0; + } + + // 전진 조건을 적용하는 메서드 + move() { + if (Random.pickNumberInRange(0, 9) >= 4) { + this.count++; + } + } + + // 자동차 상태 출력 + print() { + return `${this.name} : ${'-'.repeat(this.count)}`; + } + } + + class Racing { + constructor() { + this.carInstances = []; + } + + // 쉼표 기준 분리 | 공백 처리 | 5자 이하 처리 | 중복 처리 + validateCarNames(names) { + return [...new Set(names.split(",") + .map(name => name.trim()) + .filter(name => name && name.length <= 5))]; + } + + // 전진 카운트 + advanceCount(nameArray, num) { + // 인스턴스화 + this.carInstances = nameArray.map(name => new Car(name)); + + const result = []; + for (let i = 0; i < num; i++) { + result.push(this.carInstances.map(car => { + car.move(); + return car.print(); + })); + } + return result; + } + getWinner() { + // 가장 큰 값 찾기 + const maxCount = Math.max(...this.carInstances.map(car => car.count)); + // 가장 큰 값과 같은 값을 가진 CarName 만 추출 + return this.carInstances.filter(car => car.count === maxCount).map(car => car.name); + } + } + + export { Car, Racing }; \ No newline at end of file diff --git a/src/View.js b/src/View.js new file mode 100644 index 0000000..ae140c0 --- /dev/null +++ b/src/View.js @@ -0,0 +1,30 @@ +import { Console } from "@woowacourse/mission-utils"; + +class View { + static print(message) { + Console.print(message); + } + + static async readLine(message) { + return await Console.readLineAsync(message); + } + + static printResults(results) { + results.forEach(result => { + result.forEach(carOutput => { + View.print(carOutput); + }); + }); + } + + static printWinner(winner) { + View.print(`최종 우승자 : ${winner.join(', ')}`); + } + + static printError(message) { + View.print(message); + } + } + + export default View; + \ No newline at end of file From ca40960491e63915cad07e637b9c3be0606a3e2b Mon Sep 17 00:00:00 2001 From: yuni Date: Fri, 14 Feb 2025 21:28:44 +0900 Subject: [PATCH 12/13] =?UTF-8?q?=F0=9F=93=84=20Update=20README.md?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index f9c8de1..c5c26fa 100644 --- a/README.md +++ b/README.md @@ -46,8 +46,14 @@ b. 우승자 저장 ## MVC 패턴 +src/ +│ +├── App.js # Controller (App 클래스) +├── Model.js # Model (Car, Racing 클래스) +├── View.js # View (출력 및 사용자 인터페이스 담당) +└── index.js # 애플리케이션 시작점 + +### Model (Data) - Car, Racing +### View (UI) - Console 입출력 +### Controller (Interface) - App -### Main -### Model (Data) -### View (UI) -### Controller (Interface) \ No newline at end of file From 20a4f80ce83a75d089e8633f5ca98a7fcbb0b338 Mon Sep 17 00:00:00 2001 From: yuni Date: Fri, 14 Feb 2025 23:42:53 +0900 Subject: [PATCH 13/13] =?UTF-8?q?=E2=9C=A8=20Feat=20=EA=B8=B0=EB=8A=A5?= =?UTF-8?q?=EB=B3=84=20=ED=85=8C=EC=8A=A4=ED=8A=B8=EC=BC=80=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 예외처리 관련 테스트 게이스를 추가하였다. --- __tests__/ApplicationTest.js | 72 ++++++++++++++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/__tests__/ApplicationTest.js b/__tests__/ApplicationTest.js index 0260e7e..078ddbe 100644 --- a/__tests__/ApplicationTest.js +++ b/__tests__/ApplicationTest.js @@ -1,6 +1,7 @@ import App from "../src/App.js"; import { MissionUtils } from "@woowacourse/mission-utils"; +//메서드 mocking : 실제 입력대신, 테스트에서 주어진 inputs 배열에서 값 차례대로 반환 const mockQuestions = (inputs) => { MissionUtils.Console.readLineAsync = jest.fn(); @@ -57,4 +58,75 @@ describe("자동차 경주", () => { // then await expect(app.run()).rejects.toThrow("[ERROR]"); }); + + test("이름 오류 처리", async () => { + const inputs = ["pobi, ,woni"]; + mockQuestions(inputs); + const app = new App(); + await expect(app.run()).rejects.toThrow("[ERROR]"); + }); + + test("이름 길이 오류 처리", async () => { + const inputs = ["longname,short"]; + mockQuestions(inputs); + const app = new App(); + await expect(app.run()).rejects.toThrow("[ERROR]"); + }); + + test("횟수 오류 처리", async () => { + const inputs = ["abc"]; // 잘못된 숫자 입력 + mockQuestions(inputs); + const app = new App(); + await expect(app.run()).rejects.toThrow("[ERROR]"); + }); + + test("횟수 숫자 오류 처리", async () => { + const inputs = ["pobi,woni", "abc"]; + mockQuestions(inputs); + const app = new App(); + await expect(app.run()).rejects.toThrow("[ERROR]"); + }); + + test("이름 양쪽 공백 처리", async () => { + // given + const MOVING_FORWARD = 4; + const STOP = 3; + const inputs = [" pobi , woni ", "1"]; + const logs = ["pobi : -", "woni : ", "최종 우승자 : pobi"]; + const logSpy = getLogSpy(); + + mockQuestions(inputs); + mockRandoms([MOVING_FORWARD, STOP]); + + // when + const app = new App(); + await app.run(); + + // then + logs.forEach((log) => { + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(log)); + }); + }); + + test("이름 중복 처리", async () => { + // given + const MOVING_FORWARD = 4; + const STOP = 3; + const inputs = ["pobi,pobi ", "1"]; + const logs = ["pobi : -", "최종 우승자 : pobi"]; + const logSpy = getLogSpy(); + + mockQuestions(inputs); + mockRandoms([MOVING_FORWARD, STOP]); + + // when + const app = new App(); + await app.run(); + + // then + logs.forEach((log) => { + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(log)); + }); + }); }); +