-
Notifications
You must be signed in to change notification settings - Fork 212
[자동차 경주] 권세빈 미션 제출합니다. #192
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
sebeeeen
wants to merge
10
commits into
woowacourse-precourse:main
Choose a base branch
from
sebeeeen:feature/racingcar
base: main
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
f5f47b4
docs: README 작성 및 기능 목록 정리
sebeeeen 4447141
test: Car 클래스 테스트 코드 작성
sebeeeen 993b8cb
test: InputValidator 테스트 코드 작성 및 구현
sebeeeen 4fa6fc3
feat: Cars 클래스 구현
sebeeeen 9b22679
feat: RacingGame 클래스 구현
sebeeeen e537a94
feat: InputView 클래스 구현
sebeeeen 5acdc52
feat: OutputView 클래스 구현
sebeeeen c7ad62f
feat: GameController 구현
sebeeeen 24cbe9e
feat: App 클래스 및 실행 진입점 구현
sebeeeen 78847ee
chore: index.js 누락 추가
sebeeeen File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1 +1,182 @@ | ||
| # javascript-racingcar-precourse | ||
| # 🏎️ 자동차 경주 게임 | ||
|
|
||
| 우아한테크코스 프리코스 2주차 미션 - 자동차 경주 게임 구현 | ||
|
|
||
| ## 📌 기능 요구사항 | ||
|
|
||
| - 주어진 횟수 동안 n대의 자동차는 전진 또는 멈출 수 있다. | ||
| - 각 자동차에 이름을 부여할 수 있다. | ||
| - 자동차 이름은 쉼표(,)를 기준으로 구분하며 이름은 5자 이하만 가능하다. | ||
| - 사용자는 몇 번의 이동을 할 것인지를 입력할 수 있어야 한다. | ||
| - 전진하는 조건은 0에서 9 사이에서 무작위 값을 구한 후 무작위 값이 4 이상일 경우이다. | ||
| - 자동차 경주 게임을 완료한 후 누가 우승했는지를 알려준다. 우승자는 한 명 이상일 수 있다. | ||
|
|
||
| ## 🚀 기능 목록 | ||
|
|
||
| ### 1. 입력 기능 | ||
| - 자동차 이름 입력받기 | ||
| - 쉼표(,)로 구분된 문자열 파싱 | ||
| - 공백 제거 처리 | ||
| - 시도 횟수 입력받기 | ||
| - 숫자 형태의 문자열 입력 | ||
|
|
||
| ### 2. 입력 검증 기능 | ||
| - 자동차 이름 유효성 검증 | ||
| - 빈 이름이 있는지 확인 | ||
| - 각 이름이 5자 이하인지 확인 | ||
| - 중복된 이름이 있는지 확인 | ||
| - 유효하지 않은 경우 `[ERROR]` 메시지와 함께 에러 발생 | ||
| - 시도 횟수 유효성 검증 | ||
| - 숫자인지 확인 | ||
| - 양의 정수인지 확인 | ||
| - 유효하지 않은 경우 `[ERROR]` 메시지와 함께 에러 발생 | ||
|
|
||
| ### 3. 자동차 도메인 | ||
| - 자동차 객체 생성 | ||
| - 이름 저장 | ||
| - 위치 초기화 (0) | ||
| - 자동차 이동 기능 | ||
| - 0~9 사이의 무작위 값 생성 | ||
| - 무작위 값이 4 이상이면 전진 | ||
| - 무작위 값이 4 미만이면 정지 | ||
| - 위치 업데이트 | ||
|
|
||
| ### 4. 게임 진행 기능 | ||
| - 자동차 목록 관리 | ||
| - 여러 대의 자동차 생성 및 관리 | ||
| - 경주 진행 | ||
| - 각 라운드마다 모든 자동차 이동 시도 | ||
| - 지정된 횟수만큼 반복 | ||
| - 각 라운드 결과 저장 | ||
|
|
||
| ### 5. 우승자 결정 기능 | ||
| - 최대 이동 거리 계산 | ||
| - 우승자 찾기 | ||
| - 최대 거리를 가진 자동차 모두 찾기 | ||
| - 우승자 목록 반환 | ||
|
|
||
| ### 6. 출력 기능 | ||
| - 실행 결과 출력 | ||
| - 각 라운드의 자동차 상태 출력 | ||
| - 자동차 이름과 위치('-' 문자로 표현) 출력 | ||
| - 각 라운드 사이 빈 줄 추가 | ||
| - 최종 우승자 출력 | ||
| - 단독 우승자: `최종 우승자 : pobi` | ||
| - 공동 우승자: `최종 우승자 : pobi, jun` (쉼표로 구분) | ||
|
|
||
| ### 7. 테스트 기능 | ||
| - 입력 검증 테스트 | ||
| - 5자 초과 이름 테스트 | ||
| - 빈 이름 테스트 | ||
| - 중복 이름 테스트 | ||
| - 음수 횟수 테스트 | ||
| - 0 횟수 테스트 | ||
| - 자동차 이동 테스트 | ||
| - 무작위 값이 4 이상일 때 전진 테스트 | ||
| - 무작위 값이 4 미만일 때 정지 테스트 | ||
| - 우승자 결정 테스트 | ||
| - 단독 우승자 테스트 | ||
| - 공동 우승자 테스트 | ||
|
|
||
| ## 📂 프로젝트 구조 | ||
|
|
||
| ``` | ||
| src/ | ||
| ├── App.js # 애플리케이션 실행 시작점 | ||
| ├── controller/ | ||
| │ └── GameController.js # 게임 전체 흐름 제어 | ||
| ├── domain/ | ||
| │ ├── Car.js # 자동차 클래스 | ||
| │ ├── Cars.js # 자동차 목록 관리 클래스 | ||
| │ └── RacingGame.js # 경주 게임 로직 클래스 | ||
| ├── validator/ | ||
| │ └── InputValidator.js # 입력 검증 클래스 | ||
| └── view/ | ||
| ├── InputView.js # 사용자 입력 처리 | ||
| └── OutputView.js # 결과 출력 처리 | ||
|
|
||
| __tests__/ | ||
| ├── CarTest.js # 자동차 도메인 테스트 | ||
| ├── CarsTest.js # 자동차 목록 테스트 | ||
| ├── RacingGameTest.js # 게임 로직 테스트 | ||
| └── InputValidatorTest.js # 입력 검증 테스트 | ||
| ``` | ||
|
|
||
| ## 🎯 프로그래밍 요구사항 | ||
|
|
||
| - Node.js 22.19.0 버전에서 실행 가능 | ||
| - indent depth 2 이하로 제한 | ||
| - 3항 연산자 사용 금지 | ||
| - 함수는 한 가지 일만 수행하도록 작게 구현 | ||
| - Jest를 이용한 테스트 코드 작성 | ||
| - `@woowacourse/mission-utils`의 `Random`, `Console` API 사용 | ||
|
|
||
| ## 💻 실행 방법 | ||
|
|
||
| ### 패키지 설치 | ||
| ```bash | ||
| npm install | ||
| ``` | ||
|
|
||
| ### 프로그램 실행 | ||
| ```bash | ||
| npm run start | ||
| ``` | ||
|
|
||
| ### 테스트 실행 | ||
| ```bash | ||
| npm run test | ||
| ``` | ||
|
|
||
| ## 📝 실행 예시 | ||
|
|
||
| ``` | ||
| 경주할 자동차 이름을 입력하세요.(이름은 쉼표(,) 기준으로 구분) | ||
| pobi,woni,jun | ||
| 시도할 횟수는 몇 회인가요? | ||
| 5 | ||
|
|
||
| 실행 결과 | ||
| pobi : - | ||
| woni : | ||
| jun : - | ||
|
|
||
| pobi : -- | ||
| woni : - | ||
| jun : -- | ||
|
|
||
| pobi : --- | ||
| woni : -- | ||
| jun : --- | ||
|
|
||
| pobi : ---- | ||
| woni : --- | ||
| jun : ---- | ||
|
|
||
| pobi : ----- | ||
| woni : ---- | ||
| jun : ----- | ||
|
|
||
| 최종 우승자 : pobi, jun | ||
| ``` | ||
|
|
||
| ## 🔍 주요 구현 내용 | ||
|
|
||
| ### 객체지향 설계 | ||
| - 단일 책임 원칙(SRP)을 지키며 각 클래스가 하나의 역할만 수행 | ||
| - 도메인 로직과 입출력 로직 분리 | ||
| - 검증 로직의 독립적인 관리 | ||
|
|
||
| ### 에러 처리 | ||
| - 모든 잘못된 입력에 대해 `[ERROR]`로 시작하는 메시지 출력 | ||
| - `throw new Error()` 사용 | ||
|
|
||
| ### 함수 분리 | ||
| - indent depth를 줄이기 위해 함수를 작은 단위로 분리 | ||
| - 각 함수가 명확한 하나의 책임만 수행 | ||
|
|
||
| ## 📚 참고 자료 | ||
|
|
||
| - [AngularJS Git Commit Message Conventions](https://gist.github.com/stephenparish/9941e89d80e2bc58a153) | ||
| - [JavaScript Style Guide](https://github.com/airbnb/javascript) | ||
| - [Jest 공식 문서](https://jestjs.io/) |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,66 @@ | ||
| import Cars from '../src/domain/Cars.js'; | ||
| import Car from '../src/domain/Car.js'; | ||
|
|
||
| describe('Cars 클래스 테스트', () => { | ||
| test('이름 배열로 여러 자동차를 생성한다', () => { | ||
| const names = ['pobi', 'woni', 'jun']; | ||
| const cars = new Cars(names); | ||
|
|
||
| expect(cars.getCars()).toHaveLength(3); | ||
| }); | ||
|
|
||
| test('모든 자동차를 한 번씩 이동시킨다', () => { | ||
| const cars = new Cars(['pobi', 'woni']); | ||
|
|
||
| cars.moveAll([4, 3]); | ||
|
|
||
| const carsList = cars.getCars(); | ||
| expect(carsList[0].getPosition()).toBe(1); | ||
| expect(carsList[1].getPosition()).toBe(0); | ||
| }); | ||
|
|
||
| test('각 자동차에 다른 무작위 값을 적용한다', () => { | ||
| const cars = new Cars(['pobi', 'woni', 'jun']); | ||
|
|
||
| cars.moveAll([5, 2, 8]); | ||
|
|
||
| const carsList = cars.getCars(); | ||
| expect(carsList[0].getPosition()).toBe(1); | ||
| expect(carsList[1].getPosition()).toBe(0); | ||
| expect(carsList[2].getPosition()).toBe(1); | ||
| }); | ||
|
|
||
| test('최대 위치를 가진 자동차들을 찾는다 - 단독 우승자', () => { | ||
| const cars = new Cars(['pobi', 'woni', 'jun']); | ||
|
|
||
| cars.moveAll([5, 4, 3]); | ||
| cars.moveAll([6, 3, 2]); | ||
|
|
||
| const winners = cars.getWinners(); | ||
|
|
||
| expect(winners).toHaveLength(1); | ||
| expect(winners[0].getName()).toBe('pobi'); | ||
| }); | ||
|
|
||
| test('최대 위치를 가진 자동차들을 찾는다 - 공동 우승자', () => { | ||
| const cars = new Cars(['pobi', 'woni', 'jun']); | ||
|
|
||
| cars.moveAll([5, 6, 3]); | ||
| cars.moveAll([6, 5, 2]); | ||
|
|
||
| const winners = cars.getWinners(); | ||
|
|
||
| expect(winners).toHaveLength(2); | ||
| expect(winners.map(car => car.getName())).toEqual(expect.arrayContaining(['pobi', 'woni'])); | ||
| }); | ||
|
|
||
| test('모든 자동차가 같은 위치면 모두 우승자다', () => { | ||
| const cars = new Cars(['pobi', 'woni', 'jun']); | ||
|
|
||
| cars.moveAll([3, 3, 3]); | ||
|
|
||
| const winners = cars.getWinners(); | ||
|
|
||
| expect(winners).toHaveLength(3); | ||
| }); | ||
| }); | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,46 @@ | ||
| import Car from '../src/domain/Car.js'; | ||
|
|
||
| describe('Car 클래스 테스트', () => { | ||
| test('자동차 생성 시 이름이 저장된다', () => { | ||
| const car = new Car('pobi'); | ||
|
|
||
| expect(car.getName()).toBe('pobi'); | ||
| }); | ||
|
|
||
| test('자동차 생성 시 초기 위치는 0이다', () => { | ||
| const car = new Car('pobi'); | ||
|
|
||
| expect(car.getPosition()).toBe(0); | ||
| }); | ||
|
|
||
| test('무작위 값이 4 이상이면 전진한다', () => { | ||
| const car = new Car('pobi'); | ||
|
|
||
| car.move(4); | ||
| expect(car.getPosition()).toBe(1); | ||
|
|
||
| car.move(9); | ||
| expect(car.getPosition()).toBe(2); | ||
| }); | ||
|
|
||
| test('무작위 값이 4 미만이면 멈춘다', () => { | ||
| const car = new Car('pobi'); | ||
|
|
||
| car.move(3); | ||
| expect(car.getPosition()).toBe(0); | ||
|
|
||
| car.move(0); | ||
| expect(car.getPosition()).toBe(0); | ||
| }); | ||
|
|
||
| test('여러 번 이동 시 위치가 누적된다', () => { | ||
| const car = new Car('pobi'); | ||
|
|
||
| car.move(4); | ||
| car.move(5); | ||
| car.move(3); | ||
| car.move(6); | ||
|
|
||
| expect(car.getPosition()).toBe(3); | ||
| }); | ||
| }); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,85 @@ | ||
| import InputValidator from '../src/validator/InputValidator.js'; | ||
|
|
||
| describe('InputValidator 클래스 테스트', () => { | ||
| describe('자동차 이름 검증', () => { | ||
| test('빈 이름이 있으면 에러가 발생한다', () => { | ||
| expect(() => { | ||
| InputValidator.validateCarNames(['pobi', '', 'jun']); | ||
| }).toThrow('[ERROR]'); | ||
| }); | ||
|
|
||
| test('5자를 초과하는 이름이 있으면 에러가 발생한다', () => { | ||
| expect(() => { | ||
| InputValidator.validateCarNames(['pobi', 'longname']); | ||
| }).toThrow('[ERROR]'); | ||
| }); | ||
|
|
||
| test('중복된 이름이 있으면 에러가 발생한다', () => { | ||
| expect(() => { | ||
| InputValidator.validateCarNames(['pobi', 'woni', 'pobi']); | ||
| }).toThrow('[ERROR]'); | ||
| }); | ||
|
|
||
| test('자동차 이름이 하나도 없으면 에러가 발생한다', () => { | ||
| expect(() => { | ||
| InputValidator.validateCarNames([]); | ||
| }).toThrow('[ERROR]'); | ||
| }); | ||
|
|
||
| test('유효한 자동차 이름들은 에러가 발생하지 않는다', () => { | ||
| expect(() => { | ||
| InputValidator.validateCarNames(['pobi', 'woni', 'jun']); | ||
| }).not.toThrow(); | ||
| }); | ||
|
|
||
| test('1글자 이름도 유효하다', () => { | ||
| expect(() => { | ||
| InputValidator.validateCarNames(['a', 'b', 'c']); | ||
| }).not.toThrow(); | ||
| }); | ||
|
|
||
| test('5글자 이름은 유효하다', () => { | ||
| expect(() => { | ||
| InputValidator.validateCarNames(['abcde', 'fghij']); | ||
| }).not.toThrow(); | ||
| }); | ||
| }); | ||
|
|
||
| describe('시도 횟수 검증', () => { | ||
| test('음수이면 에러가 발생한다', () => { | ||
| expect(() => { | ||
| InputValidator.validateRounds(-1); | ||
| }).toThrow('[ERROR]'); | ||
| }); | ||
|
|
||
| test('0이면 에러가 발생한다', () => { | ||
| expect(() => { | ||
| InputValidator.validateRounds(0); | ||
| }).toThrow('[ERROR]'); | ||
| }); | ||
|
|
||
| test('숫자가 아니면 에러가 발생한다', () => { | ||
| expect(() => { | ||
| InputValidator.validateRounds('abc'); | ||
| }).toThrow('[ERROR]'); | ||
| }); | ||
|
|
||
| test('소수이면 에러가 발생한다', () => { | ||
| expect(() => { | ||
| InputValidator.validateRounds(3.5); | ||
| }).toThrow('[ERROR]'); | ||
| }); | ||
|
|
||
| test('양의 정수는 유효하다', () => { | ||
| expect(() => { | ||
| InputValidator.validateRounds(5); | ||
| }).not.toThrow(); | ||
| }); | ||
|
|
||
| test('1도 유효하다', () => { | ||
| expect(() => { | ||
| InputValidator.validateRounds(1); | ||
| }).not.toThrow(); | ||
| }); | ||
| }); | ||
| }); |
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
위 3개의 테스트의 경우에는
test.each로 한 번에 처리가 가능할 것 같습니다!우테코에서 알려준 블로그