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

[자동차 경주] 서준배 미션 제출합니다. #440

Open
wants to merge 8 commits into
base: main
Choose a base branch
from
Open
35 changes: 35 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,36 @@
# javascript-racingcar-precourse

## 요구사항

- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
- 각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다.
- 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다.
- 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다.
- 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다.
- 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다.
- 우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다.
- 사용자가 잘못된 값을 입력할 경우 "[ERROR]"로 시작하는 메시지와 함께 Error를 발생시킨 후 애플리케이션은 종료되어야 한다.

## 구현할 기능 목록

- 입력 폼을 구현
- 쉽표를 기준으로 구분하여 자동차 입력받기
- 해쉬맵으로 구현한다.
- 시도할 횟수를 입력
- 경주게임 구현
- 0-9의 무작위 숫자 얻기
- 진행상황 출력
- 입력된 자동차 순서대로 출력
- 우승자 출력
- 공동우승자는 입력 순서대로 출력
- ,로 구분
- 만약 우승자가 없다면, 우승자가 없다고 출력
- 예외, 오류
- 자동차 이름
- 6글자이상이거나 빈글자는 오류
- 대소문자를 구분한다.
- 중복된 이름은 오류 처리
- 영어로된 이름이어야 한다.
- 시도횟수
- 1이상의 숫자만 가능
- 0또는 빈숫자, 숫자 이외의 입력은 오류처리
115 changes: 114 additions & 1 deletion __tests__/ApplicationTest.js
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,47 @@ describe("자동차 경주", () => {
});
});

test("예외 테스트", async () => {
test("공동우승자 확인", async () => {
// given
const MOVING_FORWARD = 4;
const inputs = ["pobi,woni", "2"];
const logs = ["pobi : -", "woni : -", "최종 우승자 : pobi, woni"];
const logSpy = getLogSpy();

mockQuestions(inputs);
mockRandoms([MOVING_FORWARD, MOVING_FORWARD]);

// when
const app = new App();
await app.run();

// then
logs.forEach((log) => {
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(log));
});
});

test("이동을 둘다 못해서 우승자 없음", async () => {
// given
const STOP = 3;
const inputs = ["pobi,woni", "2"];
const logs = ["pobi : ", "woni : ", "최종 우승자 : 없음"];
const logSpy = getLogSpy();

mockQuestions(inputs);
mockRandoms([STOP, STOP]);

// when
const app = new App();
await app.run();

// then
logs.forEach((log) => {
expect(logSpy).toHaveBeenCalledWith(expect.stringContaining(log));
});
});

test("[예외]5글자를 넘은 자동차 이름 예외", async () => {
// given
const inputs = ["pobi,javaji"];
mockQuestions(inputs);
Expand All @@ -57,4 +97,77 @@ describe("자동차 경주", () => {
// then
await expect(app.run()).rejects.toThrow("[ERROR]");
});

test("[예외]숫자가 들어간 자동차는 예외", async () => {
// given
const inputs = ["pobi,pobi2"];
mockQuestions(inputs);

// when
const app = new App();

// then
await expect(app.run()).rejects.toThrow("[ERROR]");
});

test("[예외]빈 자동차 이름 예외", async () => {
// given
const inputs = ["pobi,,java"];
mockQuestions(inputs);

// when
const app = new App();

// then
await expect(app.run()).rejects.toThrow("[ERROR]");
});

test("[예외]중복 자동차 이름 예외", async () => {
// given
const inputs = ["pobi,pobi,java"];
mockQuestions(inputs);

// when
const app = new App();

// then
await expect(app.run()).rejects.toThrow("[ERROR]");
});
test("[예외]대소문자 중복 자동차 이름 예외", async () => {
// given
const inputs = ["poBi,poBi,java"];
mockQuestions(inputs);

// when
const app = new App();

// then
await expect(app.run()).rejects.toThrow("[ERROR]");
});

test("[예외]이동횟수에 숫자가 0이면 예외", async () => {
// given
const inputs = ["pobi,woni", "0"];

mockQuestions(inputs);

// when
const app = new App();

// then
await expect(app.run()).rejects.toThrow("[ERROR]");
});

test("[예외]이동횟수에 숫자가 아니면 예외", async () => {
// given
const inputs = ["pobi,woni", "a"];

mockQuestions(inputs);

// when
const app = new App();

// then
await expect(app.run()).rejects.toThrow("[ERROR]");
});
});
115 changes: 114 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,118 @@
import { MissionUtils, Console } from "@woowacourse/mission-utils";

class App {
async run() {}
constructor() {
this.carDistanceMap = new Map();
this.attemptCount = 0;
this.maxDistance = -1;
this.MOVING_FORWARD = 4;
}

async run() {
const carNamesInput = await Console.readLineAsync(
"경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)\n"
);
this.carInit(carNamesInput);

const attemptCountInput = await Console.readLineAsync(
"시도할 횟수는 몇 회인가요?\n"
);
this.checkAttemptCount(attemptCountInput);
this.attemptCount = Number(attemptCountInput);
this.playRacing();

this.racingResult();
}

checkAttemptCount(attemptCount) {
const regex = new RegExp("^[0-9]+$");
if (!regex.test(attemptCount)) {
throw new Error("[ERROR]");
}
if (Number(attemptCount) === 0) {
throw new Error("[ERROR]");
}
}

/**
* 자동차이름들 입력
* ,로 구분하여 carDistanceMap에 저장
* 초기는 0으로 저장한다.
* @param {*} carNamesInput
*/
carInit(carNamesInput) {
let carNames = carNamesInput.split(",");

carNames.forEach((carName) => {
this.checkCarName(carName);
this.carDistanceMap.set(carName, 0);
});
}

/**
* 1~5글자 사이인지 확인
* 영문만 허락한다.
* 중복을 걸러낸다.
* @param {*} carName
*/
checkCarName(carName) {
const regex = new RegExp("^[A-Za-z]{1,5}$");
if (!regex.test(carName)) {
throw new Error("[ERROR]");
}

if (this.carDistanceMap.has(carName)) {
throw new Error("[ERROR]");
}
}

/**
* 레이싱 게임을 진행
* 시도할 횟수만큼 반복한다.
* random을 통해 4(MOVING_FORWARD)이상일때만 이동을 진행한다.
*/
playRacing() {
if (this.attemptCount === 0) return;

Console.print("실행 결과");
this.carDistanceMap.forEach((moveDistance, carName) => {
const randomNumber = MissionUtils.Random.pickNumberInRange(0, 9);
//MOVING_FORWARD이상이면 이동
if (randomNumber >= this.MOVING_FORWARD) {
this.carDistanceMap.set(carName, moveDistance + 1);
this.maxDistance = Math.max(moveDistance + 1, this.maxDistance);
}
this.printCar(carName);
});

this.attemptCount--;
this.playRacing();
}

/**
* 현재 자동차의 상태 출력
*/
printCar(carName) {
let moveDistance = this.carDistanceMap.get(carName);

//n번 반복
Console.print(`${carName} : ${"-".repeat(moveDistance)}`);
}

racingResult() {
let winnerCarNames = [];
this.carDistanceMap.forEach((moveDistance, carName) => {
if (this.maxDistance === moveDistance) {
winnerCarNames.push(carName);
}
});

if (winnerCarNames.length === 0) {
Console.print(`최종 우승자 : 없음`);
} else {
Console.print(`최종 우승자 : ${winnerCarNames.join(", ")}`);
}
}
}

export default App;