diff --git a/README.md b/README.md index e078fd41f..8b775d72a 100644 --- a/README.md +++ b/README.md @@ -1 +1,21 @@ -# javascript-racingcar-precourse +# 🏎️ μžλ™μ°¨ κ²½μ£Ό κ²Œμž„ κΈ°λŠ₯ λͺ©λ‘ + +### 1️⃣ μ‚¬μš©μž μž…λ ₯ κΈ°λŠ₯ +- [ ] `κ²½μ£Όν•  μžλ™μ°¨μ˜ 이름` 은 2자 이상 5자 μ΄ν•˜λ‘œ μž…λ ₯λ°›λŠ”λ‹€. μžλ™μ°¨λŠ” μ΅œλŒ€ 5λŒ€ μž…λ ₯받을 수 μžˆλ‹€. +- [ ] `μ‹œλ„ν•  횟수` λŠ” 1회 이상 10회 μ΄ν•˜λ‘œ μž…λ ₯λ°›λŠ”λ‹€. + +#### 🚨 μ˜ˆμ™Έ 처리 +- [ ] μžλ™μ°¨ 이름 μž…λ ₯ μ‹œ 빈 λ¬Έμžμ—΄μ΄ μž…λ ₯될 경우 `[ERROR] μžλ™μ°¨ 이름을 이름을 μž…λ ₯ν•˜μ„Έμš”.` λΌλŠ” μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό 좜λ ₯ν•˜κ³  μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ μ’…λ£Œν•œλ‹€. +- [ ] μžλ™μ°¨ κ°œμˆ˜κ°€ 1λŒ€ λ―Έλ§Œμ΄κ±°λ‚˜ 5λŒ€ 초과일 경우 `[ERROR] μžλ™μ°¨ κ°œμˆ˜λŠ” 1λŒ€ 이상 5λŒ€ μ΄ν•˜μ΄μ–΄μ•Ό ν•©λ‹ˆλ‹€.` λΌλŠ” μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό 좜λ ₯ν•˜κ³  μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ μ’…λ£Œν•œλ‹€. +- [ ] μžλ™μ°¨ 이름이 5자 초과일 경우 `[ERROR] μžλ™μ°¨ 이름은 2자 이상 5자 μ΄ν•˜λ‘œ μž…λ ₯ν•΄μ•Ό ν•©λ‹ˆλ‹€.` λΌλŠ” μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό 좜λ ₯ν•˜κ³  μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ μ’…λ£Œν•œλ‹€. +- [ ] μ‹œλ„ν•  νšŸμˆ˜κ°€ μˆ«μžκ°€ μ•„λ‹ˆκ±°λ‚˜ μŒμˆ˜μ΄κ±°λ‚˜ 10을 μ΄ˆκ³Όν•  경우 `[ERROR] μ‹œλ„ νšŸμˆ˜λŠ” 1회 이상 10회 μ΄ν•˜μ˜ 숫자둜 μž…λ ₯ν•΄μ•Ό ν•©λ‹ˆλ‹€.` λΌλŠ” μ—λŸ¬ λ©”μ‹œμ§€λ₯Ό 좜λ ₯ν•˜κ³  μ• ν”Œλ¦¬μΌ€μ΄μ…˜μ„ μ’…λ£Œν•œλ‹€. + +### 2️⃣ μ‹œλ„ν•  횟수 만큼 랜덀으둜 숫자 좔첨 +- [ ] μ‹œλ„ν•  횟수 만큼 λ°˜λ³΅ν•˜μ—¬ `@woowacourse/mission-utils` μ—μ„œ μ œκ³΅ν•˜λŠ” `Random.pickNumberInRange()` λ₯Ό ν™œμš©ν•˜μ—¬ 각 μžλ™μ°¨λ§ˆλ‹€ 0λΆ€ν„° 9 μ‚¬μ΄μ˜ 랜덀 숫자λ₯Ό μΆ”μ²¨ν•œλ‹€. + +### 3️⃣ μžλ™μ°¨ 전진 처리 +- [ ] 랜덀 μˆ«μžκ°€ 4 이상일 경우 ν•΄λ‹Ή μžλ™μ°¨μ˜ μœ„μΉ˜μ— "-"λ₯Ό μΆ”κ°€ν•˜μ—¬ 전진을 λ‚˜νƒ€λ‚Έλ‹€. + +### 4️⃣ κ²°κ³Ό 좜λ ₯ κΈ°λŠ₯ +- [ ] μ‹œλ„ν•  횟수만큼 λͺ¨λ“  μ‹œλ„κ°€ λλ‚œ ν›„ 각 `μ°¨μˆ˜λ³„ μ‹€ν–‰ κ²°κ³Ό` λ₯Ό 좜λ ₯ν•œλ‹€. +- [ ] κ°€μž₯ λ§Žμ€ "-"λ₯Ό κΈ°λ‘ν•œ μžλ™μ°¨λ₯Ό `μ΅œμ’… 우승자 : ` ν˜•μ‹μœΌλ‘œ κ²°κ³Όλ₯Ό 좜λ ₯ν•œλ‹€. μš°μŠΉμžκ°€ μ—¬λŸ¬ λͺ…일 경우 μ‰Όν‘œ(,)둜 κ΅¬λΆ„ν•œλ‹€. \ No newline at end of file diff --git a/src/App.js b/src/App.js index 091aa0a5d..89bbe5aa5 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,16 @@ +import Input from './io-system/input'; +import Output from "./io-system/output"; +import Race from './Race'; + class App { - async run() {} + async run() { + const { carNames, attempts } = await Input(); + + const race = new Race(carNames, attempts); + const { winners, currentPositions } = race.play(); + + Output(winners, currentPositions); + } } -export default App; +export default App; \ No newline at end of file diff --git a/src/Car.js b/src/Car.js new file mode 100644 index 000000000..de7e5378b --- /dev/null +++ b/src/Car.js @@ -0,0 +1,14 @@ +class Car { + constructor(name) { + this.name = name; + this.position = 0; + } + + move(isMoving) { + if (isMoving) { + this.position += 1; + } + } +} + +export default Car; \ No newline at end of file diff --git a/src/Race.js b/src/Race.js new file mode 100644 index 000000000..851be283c --- /dev/null +++ b/src/Race.js @@ -0,0 +1,38 @@ +import { moveCar } from './utils/moveCar'; +import Car from './Car'; + +export default class Race { + constructor(carNames, attempts) { + this.cars = carNames.map(name => new Car(name)); + this.attempts = attempts; + } + + play() { + const currentPositions = []; + + for (let i = 0; i < this.attempts; i++) { + this.cars.forEach(car => { + const isMoving = moveCar(); + car.move(isMoving); + }); + const positions = this.printCurrentPositions(); + currentPositions.push(...positions); + } + + return { + winners: this.getWinners(), + currentPositions + }; + } + + printCurrentPositions() { + return this.cars.map(car => { + return `${car.name} : ${'-'.repeat(car.position)}`; + }); + } + + getWinners() { + const maxPosition = Math.max(...this.cars.map(car => car.position)); + return this.cars.filter(car => car.position === maxPosition).map(car => car.name); + } +} \ No newline at end of file diff --git a/src/constants/message.js b/src/constants/message.js new file mode 100644 index 000000000..481f3bfda --- /dev/null +++ b/src/constants/message.js @@ -0,0 +1,13 @@ +export const IO_MESSAGE = Object.freeze({ + INPUT_CARNAME: 'κ²½μ£Όν•  μžλ™μ°¨ 이름을 μž…λ ₯ν•˜μ„Έμš”. (이름은 μ‰Όν‘œ(,) κΈ°μ€€μœΌλ‘œ ꡬ뢄)\n', + INPUT_ATTEMPTS: 'μ‹œλ„ν•  νšŸμˆ˜λŠ” λͺ‡ νšŒμΈκ°€μš”?\n', + OUTPUT_RESULT: 'μ‹€ν–‰ κ²°κ³Ό', + OUTPUT_WINNER: 'μ΅œμ’… 우승자 :', +}); + +export const ERROR_MESSAGE = Object.freeze({ + ERROR_EMPTY_CAR_NAME: '[ERROR] μžλ™μ°¨ 이름을 μž…λ ₯ν•˜μ„Έμš”. (μ΅œμ†Œ 1개, μ΅œλŒ€ 5개 μž…λ ₯ κ°€λŠ₯)', + ERROR_INVALID_CAR_COUNT: '[ERROR] μžλ™μ°¨ κ°œμˆ˜λŠ” 1λŒ€ 이상 5λŒ€ μ΄ν•˜μ΄μ–΄μ•Ό ν•©λ‹ˆλ‹€.', + ERROR_CAR_NAME_LENGTH: '[ERROR] μžλ™μ°¨ 이름은 2자 이상 5자 μ΄ν•˜λ‘œ μž…λ ₯ν•΄μ•Ό ν•©λ‹ˆλ‹€.', + ERROR_INVALID_ATTEMPTS: '[ERROR] μ‹œλ„ νšŸμˆ˜λŠ” 1회 이상 10회 μ΄ν•˜μ˜ 숫자둜 μž…λ ₯ν•΄μ•Ό ν•©λ‹ˆλ‹€.', +}); \ No newline at end of file diff --git a/src/io-system/input.js b/src/io-system/input.js new file mode 100644 index 000000000..0e7cf4f9f --- /dev/null +++ b/src/io-system/input.js @@ -0,0 +1,33 @@ +import { Console } from "@woowacourse/mission-utils"; +import { ERROR_MESSAGE, IO_MESSAGE } from '../constants/message'; + +async function Input() { + const carNamesInput = await Console.readLineAsync(IO_MESSAGE.INPUT_CARNAME); + const attemptsInput = await Console.readLineAsync(IO_MESSAGE.INPUT_ATTEMPTS); + + const carNames = carNamesInput.split(',') + .map(name => name.trim()) + .filter(name => name !== ''); + + const attempts = Number(attemptsInput); + + if (carNames.length === 0) { + throw new Error(ERROR_MESSAGE.ERROR_EMPTY_CAR_NAME); + } + + if (carNames.length < 1 || carNames.length > 5) { + throw new Error(ERROR_MESSAGE.ERROR_INVALID_CAR_COUNT); + } + + if (carNames.some(name => name.length < 2 || name.length > 5)) { + throw new Error(ERROR_MESSAGE.ERROR_CAR_NAME_LENGTH); + } + + if (isNaN(attempts) || attempts < 1 || attempts > 10) { + throw new Error(ERROR_MESSAGE.ERROR_INVALID_ATTEMPTS); + } + + return { carNames, attempts }; +} + +export default Input; diff --git a/src/io-system/output.js b/src/io-system/output.js new file mode 100644 index 000000000..d733bcd82 --- /dev/null +++ b/src/io-system/output.js @@ -0,0 +1,17 @@ +import { Console } from "@woowacourse/mission-utils"; +import { IO_MESSAGE } from "../constants/message"; + +function Output(winners, currentPositions) { + Console.print(IO_MESSAGE.OUTPUT_RESULT); + + if (currentPositions && currentPositions.length > 0) { + currentPositions.forEach(position => { + Console.print(position); + }); + } + + const result = winners.join(', '); + Console.print(`${IO_MESSAGE.OUTPUT_WINNER} ${result}`); +} + +export default Output; \ No newline at end of file diff --git a/src/utils/moveCar.js b/src/utils/moveCar.js new file mode 100644 index 000000000..0fbb9eed1 --- /dev/null +++ b/src/utils/moveCar.js @@ -0,0 +1,8 @@ +import getRandomNumber from "./randomNumberPicker"; + +function moveCar() { + const randomNumber = getRandomNumber(0, 9); + return randomNumber >= 4; +} + +export { moveCar }; \ No newline at end of file diff --git a/src/utils/randomNumberPicker.js b/src/utils/randomNumberPicker.js new file mode 100644 index 000000000..53a3bc97f --- /dev/null +++ b/src/utils/randomNumberPicker.js @@ -0,0 +1,7 @@ +import { MissionUtils } from "@woowacourse/mission-utils"; + +function getRandomNumber(min, max) { + return MissionUtils.Random.pickNumberInRange(min, max); +} + +export default getRandomNumber; \ No newline at end of file