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
21 changes: 21 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,22 @@
# javascript-racingcar-precourse

1. 자동차 이름 처리
[Input] 사용자에게 경주할 자동차 이름 입력을 받는다.
[Logic] 입력받은 문자열을 쉼표(,) 기준으로 분리한다. (e.g., "pobi,woni,jun" -> ['pobi', 'woni', 'jun'])
[Logic] 분리된 이름 배열이 5자 이하 등 유효한지 검사한다. ([ERROR] 발생)

2. 시도 횟수 처리
[Input] 사용자에게 시도할 횟수 입력을 받는다. (App.js의 run 담당)
[Logic] 입력받은 횟수가 1 이상의 숫자인지 검사한다. ([ERROR] 발생)

3. 경주 진행
[Data] 자동차 이름 목록으로 자동차 객체(또는 클래스) 목록을 생성한다. (e.g., [{ name: 'pobi', position: 0 }, ...])
[Logic] 자동차 한 대가 무작위 값(0~9)을 받아 4 이상일 경우 전진하는 기능을 구현한다.
(Random.pickNumberInRange가 4를 반환할 때, 3을 반환할 때를 각각 테스트)
[Logic] 모든 자동차에 대해 1라운드(1회 시도)를 실행한다.
[Output] 1라운드(차수)별 실행 결과를 형식에 맞게 출력한다. (e.g., pobi : -)

4. 최종 결과
[Logic] 경주가 끝난 후, 가장 멀리 간 우승자를 찾는 기능.
(e.g., [{ name: 'pobi', position: 3 }, { name: 'woni', position: 5 }]을 넣으면 'woni'가 나오는지 테스트)
[Output] 최종 우승자를 형식에 맞게 출력한다. (공동 우승자 쉼표 구분)
17 changes: 17 additions & 0 deletions __tests__/getWinners.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { getWinners } from "../src/App.js";

test("가장 멀리 간 자동차 하나를 반환한다", () => {
const cars = [
{ name: "pobi", position: 3 },
{ name: "woni", position: 5 },
];
expect(getWinners(cars)).toEqual(["woni"]);
});

test("공동 우승자가 있으면 모두 반환한다", () => {
const cars = [
{ name: "pobi", position: 5 },
{ name: "woni", position: 5 },
];
expect(getWinners(cars)).toEqual(["pobi", "woni"]);
});
34 changes: 34 additions & 0 deletions __tests__/printRoundResult.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
import { MissionUtils } from "@woowacourse/mission-utils";
import { printRoundResult } from "../src/App.js";

describe("printRoundResult", () => {
beforeEach(() => {
// Console.print를 mock 함수로 교체
MissionUtils.Console.print = jest.fn();
});

test("자동차들의 현재 위치를 출력한다", () => {
const cars = [
{ name: "pobi", position: 2 },
{ name: "woni", position: 1 },
];

printRoundResult(cars);

// 첫 번째 자동차 출력 확인
expect(MissionUtils.Console.print).toHaveBeenCalledWith("pobi : --");
// 두 번째 자동차 출력 확인
expect(MissionUtils.Console.print).toHaveBeenCalledWith("woni : -");
// 마지막 빈 줄 출력 확인
expect(MissionUtils.Console.print).toHaveBeenCalledWith("");
});

test("자동차가 없으면 빈 줄만 출력한다", () => {
const cars = [];

printRoundResult(cars);

expect(MissionUtils.Console.print).toHaveBeenCalledTimes(1);
expect(MissionUtils.Console.print).toHaveBeenCalledWith("");
});
});
15 changes: 15 additions & 0 deletions __tests__/runRaceRound.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { runRaceRound } from "../src/App.js";

describe("runRaceRound", () => {
test("랜덤값이 4 이상이면 자동차가 전진한다", () => {
const cars = [{ name: "pobi", position: 0 }];
runRaceRound(cars, () => 4); // 항상 4 반환
expect(cars[0].position).toBe(1);
});

test("랜덤값이 3 이하면 자동차가 정지한다", () => {
const cars = [{ name: "pobi", position: 0 }];
runRaceRound(cars, () => 3); // 항상 3 반환
expect(cars[0].position).toBe(0);
});
});
11 changes: 11 additions & 0 deletions __tests__/splitByCommaClean.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { splitByCommaClean } from "../src/App.js";

test("쉼표로 구분된 문자열을 배열로 잘 분리하고, 공백도 제거해야 한다", () => {
const input = "pobi, woni, jun";

const result = splitByCommaClean(input);

const expectedAnswer = ["pobi", "woni", "jun"];

expect(result).toEqual(expectedAnswer);
});
9 changes: 9 additions & 0 deletions __tests__/validateCarNames.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { validateCarNames } from "../src/App.js";

test("모든 이름이 유효하면 에러 없이 통과한다", () => {
expect(() => validateCarNames(["pobi", "woni"])).not.toThrow();
});

test("5자 초과 이름이 있으면 에러 발생", () => {
expect(() => validateCarNames(["junseo"])).toThrow("[ERROR]");
});
9 changes: 9 additions & 0 deletions __tests__/validateTryCount.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { validateTryCount } from "../src/App.js";

test("정상적인 숫자 입력 시 반환", () => {
expect(validateTryCount("3")).toBe(3);
});

test("0 이하 입력 시 에러 발생", () => {
expect(() => validateTryCount("0")).toThrow("[ERROR]");
});
84 changes: 83 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,87 @@
import { MissionUtils } from "@woowacourse/mission-utils";

class App {
async run() {}
async run() {
try {
// 1. 자동차 이름 입력
const rawNames = await MissionUtils.Console.readLineAsync(
"경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)\n"
);
const names = splitByCommaClean(rawNames);
validateCarNames(names);

// 자동차 객체 생성
const cars = names.map((name) => ({ name, position: 0 }));

// 2. 시도 횟수 입력
const tryCountInput =
await MissionUtils.Console.readLineAsync(
"시도할 횟수는 몇 회인가요?\n"
);
const tryCount = validateTryCount(tryCountInput);

MissionUtils.Console.print("\n실행 결과");

// 3. 라운드 진행
for (let i = 0; i < tryCount; i++) {
runRaceRound(cars, () => MissionUtils.Random.pickNumberInRange(0, 9));
printRoundResult(cars); // 🔹 별도 함수 호출로 Indent 2 유지
}

// 4. 최종 우승자 출력
const winners = getWinners(cars);
MissionUtils.Console.print(`최종 우승자 : ${winners.join(", ")}`);
} catch (error) {
MissionUtils.Console.print(error.message);
throw error;
}
}
}

//1. , 구분자로 자르기
export function splitByCommaClean(input) {
return input.split(",").map((name) => name.trim());
}

//2. 이름 5자 넘는지 유효성 검사
export function validateCarNames(names) {
const isInvalid = names.some((name) => name.length === 0 || name.length > 5);
if (isInvalid) {
throw new Error("[ERROR] 자동차 이름은 1자 이상 5자 이하만 가능합니다.");
}
}

//3. 입력횟수 유효성검사
export function validateTryCount(input) {
const count = Number(input);
if (!Number.isInteger(count) || count < 1) {
throw new Error("[ERROR] 시도 횟수는 1 이상의 숫자여야 합니다.");
}
return count;
}

//4. 라운드 진행
export function runRaceRound(cars, randomPickFn) {
cars.forEach((car) => {
const number = randomPickFn();
if (number >= 4) {
car.position += 1;
}
});
}

//* 라운드 결과 출력 (indent 줄일려고 추가 신설)
export function printRoundResult(cars) {
cars.forEach((car) => {
MissionUtils.Console.print(`${car.name} : ${"-".repeat(car.position)}`);
});
MissionUtils.Console.print(""); // 라운드 구분용 빈 줄
}

//5. 우승자 선별
export function getWinners(cars) {
const max = Math.max(...cars.map((car) => car.position));
return cars.filter((car) => car.position === max).map((car) => car.name);
}

export default App;