Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
86 changes: 85 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,85 @@
# javascript-racingcar-precourse
# 🏎️ 2025 Javascript Racingcar Precourse

### 초간단 자동차 경주 게임

---

## 📋 기능 요구 사항

1. **기본 기능**

- 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다.
- 자동차에 이름을 부여할 수 있으며 전진하는 자동차의 이름을 같이 출력한다.
```
실행 결과
pobi : -
woni :
jun : -
```
- 이름은 `,`를 기준으로 구분하며 5자 이하이다.
```
경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)
pobi,woni,jun
```
- 사용자는 몇 번의 이동을 할 것인지 입력한다.
```
시도할 횟수는 몇 회인가요?
5
```
- 전진 조건
- `0 ~ 9` 사이의 무작위 값을 구한 뒤 4 이상일 경우
- 게임이 완료된 후 누가 우승했는지 출력하며 한 명 이상일 수도 있다.
- 우승자가 여러 명일 경우 `,`를 이용해 구분한다.
```
최종 우승자 : pobi, jun
```

2. **입력 예외 처리**

- 잘못된 입력이 들어온 경우 `[ERROR]`로 시작하는 메시지와 함께 `Error`를 발생시킨다.
- 프로그램 종료 시 `process.exit()`를 사용하지 않는다.
- 예외 발생 조건:
- 자동차 이름이 빈 문자열인 경우
: `[ERROR] 자동차 이름은 1자 이상이어야 합니다.` 출력 후 종료
- 자동차 이름이 5자 이상일 경우
: `[ERROR] 자동차 이름은 5자 이하이어야 합니다.` 출력 후 종료
- 중복된 자동차 이름이 있을 경우
: `[ERROR] 중복된 자동차 이름은 작성할 수 없습니다.` 출력 후 종료
- 입력 횟수가 숫자가 아닐 경우
: `[ERROR] 숫자가 아닌 값은 입력할 수 없습니다. 1 이상의 정수를 입력해주세요.` 출력 후 종료
- 입력 횟수가 음수일 경우
: `[ERROR] 시도할 횟수는 음수가 될 수 없습니다. 1 이상의 정수를 입력해주세요.` 출력 후 종료
- 입력 횟수가 소수일 경우
: `[ERROR] 시도할 횟수는 소수가 될 수 없습니다. 1 이상의 정수를 입력해주세요.` 출력 후 종료

3. **실행 결과 예시**

```bash
경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)
pobi,woni,jun
시도할 횟수는 몇 회인가요?
5

실행 결과
pobi : -
woni :
jun : -

pobi : --
woni : -
jun : --

pobi : ---
woni : --
jun : ---

pobi : ----
woni : ---
jun : ----

pobi : -----
woni : ----
jun : -----

최종 우승자 : pobi, jun
```
59 changes: 59 additions & 0 deletions __tests__/ExtraJestTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
import { validateCarName, validateRounds } from "../src/utils/validate.js";
import { MissionUtils } from "@woowacourse/mission-utils";

describe("🏎️ 자동차 경주 추가 테스트", () => {
describe("자동차 이름 검증", () => {
test.each([
["pobi,woni", ["pobi", "woni"]],
["a,b,c", ["a", "b", "c"]],
])("'%s' 입력 시 %p 반환", (input, expected) => {
expect(validateCarName(input)).toEqual(expected);
});

test("공백 포함되면 에러 발생", () => {
expect(() => validateCarName("pobi,,jun")).toThrow(
"자동차 이름은 1자 이상이어야 합니다."
);
});

test("자동차 이름이 5자 초과 시 에러 발생", () => {
expect(() => validateCarName("abcdef,pobi")).toThrow(
"자동차 이름은 5자 이하이어야 합니다."
);
});

test("중복된 이름 입력 시 에러 발생", () => {
expect(() => validateCarName("pobi,pobi")).toThrow(
"중복된 자동차 이름은 작성할 수 없습니다."
);
});
});

describe("시도 횟수 검증", () => {
test.each([
["1", 1],
["3", 3],
["10", 10],
])("'%s' 입력 시 %i 반환", (input, expected) => {
expect(validateRounds(input)).toBe(expected);
});

test.each(["0", "-1"])("음수 또는 0 입력 시 에러 발생", (input) => {
expect(() => validateRounds(input)).toThrow(
"시도할 횟수는 음수가 될 수 없습니다. 1 이상의 정수를 입력해주세요."
);
});

test("소수 입력 시 에러 발생", () => {
expect(() => validateRounds("3.5")).toThrow(
"시도할 횟수는 소수가 될 수 없습니다. 1 이상의 정수를 입력해주세요."
);
});

test("숫자가 아닌 값 입력 시 에러 발생", () => {
expect(() => validateRounds("abc")).toThrow(
"숫자가 아닌 값은 입력할 수 없습니다. 1 이상의 정수를 입력해주세요."
);
});
});
});
31 changes: 30 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,34 @@
import { Console } from "@woowacourse/mission-utils";

import { getCarName, getRounds } from "./utils/input.js";
import { validateCarName, validateRounds } from "./utils/validate.js";
import { race } from "./utils/race.js";
import { getWinners, printWinners } from "./utils/winner.js";
import Car from "./model/Car.js";

import { ERROR_MESSAGES } from "./utils/constants.js";

class App {
async run() {}
async run() {
try {
const inputCarName = await getCarName();
const carNames = validateCarName(inputCarName);

const inputRounds = await getRounds();
const rounds = validateRounds(inputRounds);

const cars = carNames.map((name) => new Car(name));
Console.print("\n실행 결과");

race(cars, rounds);

const winners = getWinners(cars);
printWinners(winners);
} catch (error) {
Console.print(`${ERROR_MESSAGES.PREFIX} ${error.message}`);
throw new Error(`${ERROR_MESSAGES.PREFIX} ${error.message}`);
}
}
}

export default App;
22 changes: 22 additions & 0 deletions src/model/Car.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import { Random } from "@woowacourse/mission-utils";

export default class Car {
// 생성자
constructor(name) {
this.name = name;
this.location = 0;
}

// 메서드
move() {
const randomValue = Random.pickNumberInRange(0, 9);

if (randomValue >= 4) {
this.location += 1;
}
}

getLocation() {
return "-".repeat(this.location);
}
}
12 changes: 12 additions & 0 deletions src/utils/constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
export const ERROR_MESSAGES = {
PREFIX: "[ERROR]",
CAR_NAME_EMPTY: "자동차 이름은 1자 이상이어야 합니다.",
CAR_NAME_LESS_5: "자동차 이름은 5자 이하이어야 합니다.",
CAR_NAME_DUPLICATE: "중복된 자동차 이름은 작성할 수 없습니다.",
ROUNT_NOT_NUMBER:
"숫자가 아닌 값은 입력할 수 없습니다. 1 이상의 정수를 입력해주세요.",
ROUND_NEGATIVE:
"시도할 횟수는 음수가 될 수 없습니다. 1 이상의 정수를 입력해주세요.",
ROUND_NOT_INT:
"시도할 횟수는 소수가 될 수 없습니다. 1 이상의 정수를 입력해주세요.",
};
11 changes: 11 additions & 0 deletions src/utils/input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Console } from "@woowacourse/mission-utils";

export async function getCarName() {
return Console.readLineAsync(
"경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)\n"
);
}

export async function getRounds() {
return Console.readLineAsync("시도할 횟수는 몇 회인가요?\n");
}
15 changes: 15 additions & 0 deletions src/utils/race.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Console } from "@woowacourse/mission-utils";

export function race(cars, rounds) {
for (let i = 0; i < rounds; i++) {
cars.forEach((car) => car.move());
printRaceStatus(cars);
}
}

function printRaceStatus(cars) {
cars.forEach((car) => {
Console.print(`${car.name} : ${car.getLocation()}`);
});
Console.print("");
}
38 changes: 38 additions & 0 deletions src/utils/validate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
import { ERROR_MESSAGES } from "./constants.js";

export function validateCarName(input) {
const carNames = input.split(",").map((name) => name.trim());

if (carNames.some((name) => name.length === 0)) {
throw new Error(ERROR_MESSAGES.CAR_NAME_EMPTY);
}

if (carNames.some((name) => name.length > 5)) {
throw new Error(ERROR_MESSAGES.CAR_NAME_LESS_5);
}

const hasDuplicate = new Set(carNames).size !== carNames.length;
if (hasDuplicate) {
throw new Error(ERROR_MESSAGES.CAR_NAME_DUPLICATE);
}

return carNames;
}

export function validateRounds(input) {
const rounds = Number(input.trim());

if (Number.isNaN(rounds)) {
throw new Error(ERROR_MESSAGES.ROUNT_NOT_NUMBER);
}

if (rounds <= 0) {
throw new Error(ERROR_MESSAGES.ROUND_NEGATIVE);
}

if (!Number.isInteger(rounds)) {
throw new Error(ERROR_MESSAGES.ROUND_NOT_INT);
}

return rounds;
}
13 changes: 13 additions & 0 deletions src/utils/winner.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { Console } from "@woowacourse/mission-utils";

export function getWinners(cars) {
const maxLocation = Math.max(...cars.map((car) => car.location));

return cars
.filter((car) => car.location === maxLocation)
.map((car) => car.name);
}

export function printWinners(winners) {
Console.print(`최종 우승자 : ${winners.join(", ")}`);
}