diff --git a/__tests__/ApplicationTest.js b/__tests__/ApplicationTest.js index f93b27c7a..a5568586d 100644 --- a/__tests__/ApplicationTest.js +++ b/__tests__/ApplicationTest.js @@ -1,5 +1,5 @@ -import App from "../src/App.js"; -import { MissionUtils } from "@woowacourse/mission-utils"; +import { MissionUtils } from '@woowacourse/mission-utils'; +import App from '../src/App.js'; const mockQuestions = (inputs) => { MissionUtils.Console.readLineAsync = jest.fn(); @@ -9,27 +9,25 @@ const mockQuestions = (inputs) => { return Promise.resolve(input); }); }; - +export default mockQuestions; const mockRandoms = (numbers) => { MissionUtils.Random.pickNumberInRange = jest.fn(); - numbers.reduce((acc, number) => { - return acc.mockReturnValueOnce(number); - }, MissionUtils.Random.pickNumberInRange); + numbers.reduce((acc, number) => acc.mockReturnValueOnce(number), MissionUtils.Random.pickNumberInRange); }; const getLogSpy = () => { - const logSpy = jest.spyOn(MissionUtils.Console, "print"); + const logSpy = jest.spyOn(MissionUtils.Console, 'print'); logSpy.mockClear(); return logSpy; }; -describe("자동차 경주 게임", () => { - test("전진-정지", async () => { +describe('자동차 경주 게임', () => { + test('전진-정지', async () => { // given const MOVING_FORWARD = 4; const STOP = 3; - const inputs = ["pobi,woni", "1"]; - const outputs = ["pobi : -"]; + const inputs = ['pobi,woni', '1']; + const outputs = ['pobi : -']; const randoms = [MOVING_FORWARD, STOP]; const logSpy = getLogSpy(); @@ -46,10 +44,7 @@ describe("자동차 경주 게임", () => { }); }); - test.each([ - [["pobi,javaji"]], - [["pobi,eastjun"]] - ])("이름에 대한 예외 처리", async (inputs) => { + test.each([[['pobi,javaji']], [['pobi,eastjun']]])('이름에 대한 예외 처리', async (inputs) => { // given mockQuestions(inputs); @@ -57,6 +52,6 @@ describe("자동차 경주 게임", () => { const app = new App(); // then - await expect(app.play()).rejects.toThrow("[ERROR]"); + await expect(app.play()).rejects.toThrow('[ERROR]'); }); }); diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 000000000..675d2d323 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,76 @@ +## 🚗레이싱 카 구현하기 +## ✅ 1주차 피드백 및 주의 사항 +1. 요구사항을 정확히 준수 +2. 커밋 메시지를 의미 있게 작성한다. +3. 이름을 통해 의도를 드러낸다. +4. 축약하지 않는다. + - 의도를 드러낼 수 있다면, 이름이 길어져도 좋다 +5. 공백도 코딩 컨벤션이다 . + - IF, FOR , WHILE 문 사이의 공백도 코딩 컨벤션이다. => IF , FOR , WHILE 문 뒤에도 습관적으로 공백을 주도록 하자 +6. 공백라인을 의미 있게 사용 +7. 들여쓰기에 space 와 tab 을 혼용하지 않는다. +8. 의미 없는 주석을 달지 않는다. + - 변수 이름, 함수(메서드) 이름을 통해 어떤 의도인지가 드러난다면, 굳이 주석을 달지 않는다. +9. EOL ( END OF LINE ) 최종 제출하는 코드에서 EOL을 확인한다. +## ✅ 기능 구현 사항 +초간단 자동차 경주 게임을 구현한다. + +주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다. +각 자동차에 이름을 부여할 수 있다. 전진하는 자동차를 출력할 때 자동차 이름을 같이 출력한다. +자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다. +사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다. +전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다. +자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다. +우승자가 여러 명일 경우 쉼표(,)를 이용하여 구분한다. +사용자가 잘못된 값을 입력한 경우 throw문을 사용해 "[ERROR]"로 시작하는 메시지를 가지는 예외를 발생시킨 후, 애플리케이션은 종료되어야 한다. + +### 1. Game Start 시에 , 경주할 자동차의 이름을 입력받는다. ( Console.readlineAsync 활용 ) + 1-1. 이름은 다섯자 이하만 가능하다. ( .length 를 통해 총 길이 어길 시 , throw 문 활용 에러 메시지 발생 후 애플리케이션 종료(?) ) + 1-2. 한 명만 진행할 수 있는 가? => + 1-3. + +### 2. " 시도할 회수는 몇 회인가요 " 출력 후( Console.print 활용 ) 사용자에게 "몇회" => 숫자를 입력받는다. (Console.readlineAsync 활용 ) + 2-1. 사용자가 몇회를 입력할 경우에 , 숫자를 입력하지 않은 경우에 => [ERROR] 숫자만 입력이 가능합니다. + 2-2. 시도할 수 있는 횟수의 제한은 없는 것인가 ? => + 2-3. 숫자를 입력헀지만 , 0보다 작은 수를 입력한 경우 => [ERROR] 0보다 큰 숫자만 입력이 가능합니다. + +### 3. 각 시도에 따른 실행 결과를 PRINT 해야 한다. + 3-1. 각각의 플레이어에게, 처음에 빈 배열( [] ) 을 생성해준다. + 3-2. 전진 조건 => 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우 + - 전진 조건 충족 시, 각 플레이어들의 배열에 - 값을 push 한다. + 3-3. TODO : 어떠한 방식으로 각 플레이어가 전진하는 지에 대해서 테스트 할 것 인가 ? + 3-4. 한번의 실행시에 모든 플레이어의 실행 결과를 출력한다. ( Console.pring 활용 ) + +### 4. 마지막 실행 종료시 , 각 플레이어들의 배열의 길이를 비교하여, 제일 긴 플레이어를 선정 한다. + 4-1. 우승자는 한명 이상일 경우가 존재한다. 여러 명일 경우, 쉼표(,) 활용하여 구분 + 4-2. 선택된 우승자를 출력해준다. ( Console.print 활용 ) + +## 실행 결과 예시 +경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분) +pobi,woni,jun + +시도할 횟수는 몇 회인가요? +5 + +실행 결과 +pobi : - +woni : +jun : - + +pobi : -- +woni : - +jun : -- + +pobi : --- +woni : -- +jun : --- + +pobi : ---- +woni : --- +jun : ---- + +pobi : ----- +woni : ---- +jun : ----- + +최종 우승자 : pobi, jun \ No newline at end of file diff --git a/src/App.js b/src/App.js index c38b30d5b..dd7e75eb2 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,83 @@ +import { Console, Random } from '@woowacourse/mission-utils'; +import Errors from './message/Errors.js'; +import Messages from './message/Messages.js'; + +function check_name_validate(player_arr) { + if (player_arr.length <= 1) throw new Error(Errors.SHORTAGE_ERROR); + const player_name_set = new Set(); + + player_arr.forEach((player_name) => { + const trimmed_name = player_name.trim(); + + if (trimmed_name === '') throw new Error(Errors.NAME_ERROR); + if (trimmed_name.length > 5) throw new Error(Errors.LENGTH_ERROR); + if (player_name_set.has(trimmed_name)) throw new Error(Errors.DUPLICATE_NAME_ERROR); + + player_name_set.add(trimmed_name); + }); +} +function check_number_validate(play_number) { + if (!play_number.match(/^\d+$/)) { + throw new Error(Errors.NUMBER_ERROR); + } +} + class App { - async play() {} + constructor() { + this.Console = Console; + } + + async play() { + const players = []; + await this.Console.readLineAsync(Messages.GAME_START).then((result) => { + const player_input = result.split(','); + check_name_validate(player_input); + player_input.map((player_name) => players.push(player_name)); + }); + + const play_number = await this.Console.readLineAsync(Messages.INSERT_COUNT); + check_number_validate(play_number); + let play_now = 0; + const players_object = players.reduce( + (obj, player) => ({ + ...obj, + [player]: [], + }), + {}, + ); + + do { + players.forEach((player_name) => { + const random_number = Random.pickNumberInRange(0, 9); + if (random_number <= 4) players_object[player_name].push('-'); + Console.print(`${player_name} : ${players_object[player_name].join('')}`); + }); + play_now += 1; + } while (play_now < play_number); + + const players_arr = Object.entries(players_object); + + const winners = players_arr.reduce( + (acc, [player_name, player_result]) => { + if (player_result.length > acc.max) { + return { + max: player_result.length, + players: [player_name], + }; + } + if (player_result.length === acc.max) { + acc.players.push(player_name); + } + return acc; + }, + { max: Number.MIN_SAFE_INTEGER, players: [] }, + ); + + Console.print(`${Messages.FINAL_WINNER} ${winners.players.join(',')}`); + } } export default App; + +// const app = new App(); +// app.play();