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
📝 기능 목록

[V] 쉼표(,)로 구분된 자동차 이름을 입력받는다.

[V] 이름은 1~5자 이내여야 하며, 공백·빈값은 허용하지 않는다.

[V] 시도 횟수를 입력받고, 1 이상의 숫자만 허용한다.

[V] 입력값이 잘못된 경우 [ERROR] 메시지를 출력하고 종료한다.

[V] 각 자동차는 이름과 위치를 가진 Car 인스턴스로 관리한다.

[V] 매 라운드마다 0~9의 무작위 값을 생성해 4 이상일 경우 전진한다.

[V] 시도 횟수만큼 모든 자동차의 이동을 반복한다.

[V] 각 라운드 결과를 pobi : -- 형식으로 출력한다.

[V] 가장 멀리 간 자동차(또는 공동 우승자)를 우승자로 출력한다.

[V] 최종 결과는 최종 우승자 : pobi, jun 형식으로 표시한다.
51 changes: 50 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,54 @@
import InputView from "./InputView";
import OutputView from "./OutputView";
import Validator from "./Validator";
import RacingGame from "./RacingGame";
import { PROMPT_MESSAGES,ERROR_MESSAGES } from "./Constants";
class App {
async run() {}
#game;
#attemptCount;

async run() {
try{
await this.setupGame();
this.playGame();
this.showResults();
}catch(error){
OutputView.printMessage(error.message);
throw error;
}
}

async setupGame(){
const carNames=await this.getValidCarNames();
this.#attemptCount = await this.getValidAttemptCount();

this.#game = new RacingGame(carNames);
}

playGame(){
OutputView.printMessage(PROMPT_MESSAGES.RESULT_HEADER);

for (let i = 0; i < this.#attemptCount; i++) {
this.#game.playRound();
OutputView.printRoundResult(this.#game.getRoundResult());
}
}
showResults(){
const winners=this.#game.getWinners();
OutputView.printWinners(winners);
}

async getValidCarNames(){
const carNames=await InputView.readCarNames();
Validator.validateCarNames(carNames);
return carNames;
}

async getValidAttemptCount(){
const attemptCount = await InputView.readAttemptCount();
Validator.validateAttemptCount(attemptCount);
return attemptCount;
}
}

export default App;
27 changes: 27 additions & 0 deletions src/Car.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@

import {GAME_RULES} from './Constants.js';

class Car{
#name;
#position;

constructor(name){
this.#name=name;
this.#position=0;
}

move(randomNumber){
if(randomNumber>=GAME_RULES.MOVE_FORWARD_CONDITION){
this.#position+=1;
}
}

getName(){
return this.#name;
}

getPosition(){
return this.#position;
}
}
export default Car;
16 changes: 16 additions & 0 deletions src/Constants.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
export const GAME_RULES=Object.freeze({
MOVE_FORWARD_CONDITION:4,
MIN_RANDOM_NUMBER:0,
MAX_RANDOM_NUMBER:9,
});

export const ERROR_MESSAGES=Object.freeze({
INVALID_CAR_NAME_LENGTH:'자동차 이름은 5자 이하만 가능합니다.',
INVALID_ATTEMPT_COUNT:'시도 횟수는 1 이상의 숫자여야 합니다.',
});

export const PROMPT_MESSAGES=Object.freeze({
GET_CAR_NAMES:'경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)\n',
GET_ATTEMPT_COUNT:'시도할 횟수는 몇 회인가요?\n',
RESULT_HEADER:'\n실행 결과',
});
15 changes: 15 additions & 0 deletions src/InputView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Console } from "@woowacourse/mission-utils";
import { PROMPT_MESSAGES } from "./Constants";

const InputView={
async readCarNames(){
const input=await Console.readLineAsync(PROMPT_MESSAGES.GET_CAR_NAMES);
return input.split(',');
},

async readAttemptCount(){
const input=await Console.readLineAsync(PROMPT_MESSAGES.GET_ATTEMPT_COUNT);
return Number(input);
},
};
export default InputView;
21 changes: 21 additions & 0 deletions src/OutputView.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import { Console } from "@woowacourse/mission-utils";

const OutputView={
printRoundResult(roundResult){
roundResult.forEach(car=>{
const hyphens='-'.repeat(car.position);
Console.print(`${car.name} : ${hyphens}`);
});
Console.print('');
},

printWinners(winners){
const winnerString=winners.join(', ');
Console.print(`최종 우승자 : ${winnerString}`);
},

printMessage(message){
Console.print(message);
}
};
export default OutputView;
36 changes: 36 additions & 0 deletions src/RacingGame.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Random } from '@woowacourse/mission-utils';
import Car from './Car.js';
import { GAME_RULES } from './Constants';

class RacingGame {
#cars;

constructor(carNames) {
this.#cars = carNames.map(name => new Car(name));
}
playRound() {
this.#cars.forEach(car => {
const randomNumber = Random.pickNumberInRange(
GAME_RULES.MIN_RANDOM_NUMBER,
GAME_RULES.MAX_RANDOM_NUMBER
);
car.move(randomNumber);
});
}

getRoundResult() {
return this.#cars.map(car => ({
name: car.getName(),
position: car.getPosition(),
}));
}

getWinners() {
const maxPosition = Math.max(...this.#cars.map(car => car.getPosition()));

return this.#cars
.filter(car => car.getPosition() === maxPosition)
.map(car => car.getName());
}
}
export default RacingGame;
17 changes: 17 additions & 0 deletions src/Validator.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
import { ERROR_MESSAGES } from "./Constants";

const Validator={
validateCarNames(names){
if(names.some(name=>name.length>5||name.length===0)){
throw new Error(`[ERROR] ${ERROR_MESSAGES.INVALID_CAR_NAME_LENGTH}`);
}
},

validateAttemptCount(count){
if(isNaN(count)||count<1){
throw new Error(`[ERROR] ${ERROR_MESSAGES.INVALID_ATTEMPT_COUNT}`);
}
},
};

export default Validator;
15 changes: 15 additions & 0 deletions test/Car.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import Car from '../src/Car.js';

describe('Car 테스트',()=>{
test('4 이상일 때 전진한다',()=>{
const car=new Car('pobi');
car.move(4);
expect(car.getPosition()).toBe(1);
});

test('3 이하일 때 멈춘다',()=>{
const car=new Car('pobi');
car.move(3);
expect(car.getPosition()).toBe(0);
});
});
22 changes: 22 additions & 0 deletions test/RacingGame.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import RacingGame from "../src/RacingGame";
import {Random} from '@woowacourse/mission-utils';

const mockRandom=(numbers)=>{
Random.pickNumberInRange=jest.fn();
numbers.reduce((acc,num)=>{
return acc.mockReturnValueOnce(num);

},Random.pickNumberInRange);
};

describe('RacingGame 테스트',()=>{
test('우승자 판별 (단독 우승)',()=>{
mockRandom([4,3,9]);
const game=new RacingGame(['pobi','woni','jun']);

game.playRound();

expect(game.getWinners()).toEqual(['pobi','jun']);
});

})