Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[자동차 경주] 이창준 미션 제출합니다. #441

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
93 changes: 93 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -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 테스트 코드를 작성한다.
16 changes: 14 additions & 2 deletions __tests__/ApplicationTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -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 이상의 정수를 입력해야 합니다.");
});
});
128 changes: 127 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,131 @@
import { Console, Random } from "@woowacourse/mission-utils";

class App {
async run() {}
async run() {
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);

const winners = this.getWinners(carPositions);
Console.print(`\n최종 우승자 : ${winners.join(", ")}`);

} catch (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("[ERROR] 자동차 이름은 2개 이상 입력해야 합니다.");
}
carNames.forEach(name => {
if(name.length === 0 || name.length > 5) {
throw new Error("[ERROR] 자동차 이름은 1자 이상 5자 이하만 가능합니다.");
}
})

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 선택한 자동차가 움직이는지 확인하는 함수
* @parameter {carName: string, isMoving: boolean}
*/
carMovingCheck(car, isMoving, carPositions) {
if (isMoving) {
carPositions[car] += 1;
}
}

/**
* @author CWDll
* @describe 자동차 초기 위치(0)를 설정하는 함수
* @parameter {carNames: string[]}
*/
initializeCarPositions(carNames) {
const carPositions = {};
carNames.forEach(car => {
carPositions[car] = 0;
});
return carPositions;
}

/**
* @author CWDll
* @describe 자동차 경주를 실행하는 함수
* @parameter {carNames: string[], carPositions: Object, raceCount: number}
*/
runRace(carNames, carPositions, raceCount) {
for(let i=0 ; i < raceCount; i++) {
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}`);
}

/**
* @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;
}
}




export default App;