diff --git a/__tests__/ValidatorTest.js b/__tests__/ValidatorTest.js new file mode 100644 index 000000000..1c32826fe --- /dev/null +++ b/__tests__/ValidatorTest.js @@ -0,0 +1,27 @@ +import Validator from '../src/utils/Validator'; + +describe('Validator 함수 테스트', () => { + test('5글자 초과 시 예외 처리', () => { + const input = 'abcdefghi,abc'; + + expect(() => Validator.validCarList(input)).toThrow('[ERROR]'); + }); + + test('음의 정수 입력 시 예외 처리', () => { + const input = '-1'; + + expect(() => Validator.rangeOverZero(input)).toThrow('[ERROR]'); + }); + + test('정수 이외의 입력값에 대한 예외 처리', () => { + const input = 'abcd'; + + expect(() => Validator.rangeOverZero(input)).toThrow('[ERROR]'); + }); + + test('5글자 초과 시 false 반환', () => { + const input = 'abcdef'; + + expect(Validator.validLength(input, { maxLength: 5 })).toBe(false); + }); +}); diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..393271602 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,14 @@ +## 구현할 기능 목록 + +- [x] 경주할 자동차 이름을 입력받는다. 입력받은 자동차 이름 각각에 대해 `Car` 인스턴스를 생성한다. +- [x] 시도할 게임 횟수를 입력받는다. +- [x] 경주할 자동차에 대해 각각 0 ~ 9사이의 정수 중 무작위 값을 구한 후 4이상인 경우 이동한다. +- [x] 경주 게임 우승자는 이동 경로가 가장 긴 자동차이다. 게임 횟수가 0이 됐을 때(게임 완료) 우승한 자동차 이름을 쉼표(,)로 구분해 출력한다. +- [x] 사용자 입력을 검증한다. + - [x] 경주할 자동차 이름에 대한 검증 + - [x] 시도할 횟수에 대한 검증 + - [x] 검증 완료 후 에러 발생 시 `[ERROR]` 로 시작하는 에러 문구와 함께 예외를 throw하고 애플리케이션을 종료한다. + +## 테스트 코드 목록 + +- [x] 사용자 입력 검증 기능 테스트 코드 diff --git a/src/App.js b/src/App.js index c38b30d5b..27a3aeb3a 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,10 @@ +import Game from './Game.js'; + class App { - async play() {} + async play() { + const game = new Game(); + await game.play(); + } } export default App; diff --git a/src/Car.js b/src/Car.js new file mode 100644 index 000000000..9a115e170 --- /dev/null +++ b/src/Car.js @@ -0,0 +1,29 @@ +import { Random } from '@woowacourse/mission-utils'; + +class Car { + #name = ''; + #path = ''; + + constructor(name) { + this.#name = name; + } + + move() { + const value = Random.pickNumberInRange(0, 9); + if (Number(value) < 4) { + return; + } + + this.#path += '-'; + } + + getName() { + return this.#name; + } + + getPath() { + return this.#path; + } +} + +export default Car; diff --git a/src/Game.js b/src/Game.js new file mode 100644 index 000000000..f15bb5aff --- /dev/null +++ b/src/Game.js @@ -0,0 +1,69 @@ +import { Console } from '@woowacourse/mission-utils'; + +import Car from './Car.js'; +import Input from './Input.js'; +import Validator from './utils/Validator.js'; + +class Game { + #cars = []; + #count = 0; + + async play() { + await this.setCars(); + await this.setCount(); + + Console.print('실행결과'); + + for (let i = 0; i < this.#count; i++) { + this.#cars.forEach((car) => { + car.move(); + Console.print(`${car.getName()} : ${car.getPath()}`); + }); + Console.print(''); + } + + this.showWinner(); + } + + async setCars() { + const input = new Input(); + + const value = await input.readLine( + '경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분)\n', + { + validator: Validator.validCarList, + } + ); + // TODO: 쉼표로 구분된 자동자 이름를 배열로 변환해주는 유틸함수 작성 + this.#cars = value.split(',').map((name) => new Car(name)); + } + + async setCount() { + const input = new Input(); + + const value = await input.readLine('시도할 횟수는 몇 회인가요?\n', { + validator: Validator.rangeOverZero, + }); + + this.#count = Number(value); + } + + showWinner() { + let maximumPathLength = 0; + this.#cars.forEach((car) => { + const path = car.getPath(); + maximumPathLength = Math.max(maximumPathLength, path.length); + }); + + const winners = this.#cars + .filter((car) => { + const path = car.getPath(); + return maximumPathLength === path.length; + }) + .map((car) => car.getName()); + + Console.print(`최종 우승자 : ${winners.join(', ')}`); + } +} + +export default Game; diff --git a/src/Input.js b/src/Input.js new file mode 100644 index 000000000..92c3bd2f1 --- /dev/null +++ b/src/Input.js @@ -0,0 +1,19 @@ +import { Console } from '@woowacourse/mission-utils'; + +class Input { + async readLine(query, { validator }) { + try { + const input = await Console.readLineAsync(query); + + if (typeof validator === 'function') { + validator(input); + } + + return input; + } catch (error) { + throw error; + } + } +} + +export default Input; diff --git a/src/utils/Validator.js b/src/utils/Validator.js new file mode 100644 index 000000000..fec57b8e3 --- /dev/null +++ b/src/utils/Validator.js @@ -0,0 +1,28 @@ +function rangeOverZero(input) { + if (!Number.isSafeInteger(Number(input))) { + throw new Error('[ERROR] 올바른 정수를 입력해주세요'); + } + if (Number(input) < 0) { + throw new Error('[ERROR] 음이 아닌 정수를 입력해주세요'); + } +} + +function validLength(input, { minLength = 1, maxLength }) { + return minLength <= input.length && input.length <= maxLength; +} + +function validCarList(input) { + const list = input.split(','); + if (list.every((item) => validLength(item, { maxLength: 5 }))) { + return; + } + throw new Error('[ERROR] 자동차 이름을 1글자 이상 5글자 이하로 작성해주세요'); +} + +const Validator = { + rangeOverZero, + validLength, + validCarList, +}; + +export default Validator; diff --git a/src/utils/index.js b/src/utils/index.js new file mode 100644 index 000000000..031e0cf2c --- /dev/null +++ b/src/utils/index.js @@ -0,0 +1 @@ +export * from './Validator.js';