-
Notifications
You must be signed in to change notification settings - Fork 168
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
[1단계 - 콘솔 기반 로또 게임] 초코(강다빈) 미션 제출합니다. (#290)
* docs REQUIREMENTS.md 생성 Co-authored-by: [email protected] <00kang> * docs: test * docs: test 하기 테스트중입니다 Co-authored-by: vi-wolhwa <[email protected]> * docs: 기능 구현 목록 작성 Co-authored-by: vi-wolhwa <[email protected]> * chore: eslint, prettier 설정 및 package 정보 변경 Co-authored-by: vi-wolhwa <[email protected]> * test: 구입 금액에 따른 로또 발행 테스트 작성 Co-authored-by: vi-wolhwa <[email protected]> * chore: eslint 플러그인 재설치 Co-authored-by: 00kang <[email protected]> * feat: 구입금액에 따른 발행 수량 계산 - 구입금액에 대한 발행 수량 반환 - 구입금액에 대한 유효성검사 (정수, 최솟값) Co-authored-by: 00kang <[email protected]> * feat: 랜덤 유틸 클래스 구현 - 특정 범위 내 난수 발생 함수 - 특정 범위 내 조합 추출 함수 Co-authored-by: 00kang <[email protected]> * feat: 수량 만큼 로또 발행 - 랜덤으로 로또 번호 생성 - 수량 만큼 로또 발행 Co-authored-by: 00kang <[email protected]> * feat: 로또 객체 생성 및 번호 정렬 - 로또 객체 생성 시 유효성 검사 - 로또 번호 오름차순 정렬 Co-authored-by: 00kang <[email protected]> * feat: 당첨 결과 분석, 순위 반환 - 로또번호 일치 개수 분석 - 보너스번호 포함 여부 분석 - 로또 결과(순위) 판단 Co-authored-by: 00kang <[email protected]> * style: printWidth 변경 (80 -> 100) - 불필요한 줄바꿈 개선 Co-authored-by: 00kang <[email protected]> * feat: 당첨 결과 분석 및 순위 반환 - 등수 별 당첨 수량 계산 - 당첨 결과에 따른 수익률 계산 Co-authored-by: 00kang <[email protected]> * refactor: 로또 등수 계산 로직 분리 - 함수 depth 최대 1로 리팩토링 Co-authored-by: 00kang <[email protected]> * refactor: 상수명 변경 - 복수형으로 변경 Co-authored-by: 00kang <[email protected]> * feat: 콘솔 입력 구현 - 콘솔 입력 모듈 readLineAsync 작성 - 구입금액, 당첨번호, 보너스번호, 재시작응답 입력 Co-authored-by: 00kang <[email protected]> * feat: controller, model, view 연결 Co-authored-by: 00kang <[email protected]> * feat: README.md 수정 * style: 입,출력 메시지의 스타일 수정 * feat: 로또 발행 메서드 컨트롤러에 연결 * fix: 컨트롤러에서 당첨번호 입력 기능의 inputWinningNumbers 메서드 수정 * fix: 당첨 통계 결과 반환 안되는 오류 수정 * fix: 발행된 로또 오름차순 정렬하여 보여주도록 수정 * feat: 실행결과 사진 포함하여 README.md 수정 * feat: 구입 금액에 대한 유효성 검사 진행 및 재입력받도록 추가구현 * feat: 당첨 번호 입력에 대한 유효성 검사 진행 및 재입력받도록 추가구현 * feat: 보너스 점수 입력에 대한 유효성 검사 추가 구현 * feat: 게임 재시작 및 유효성 검사 추가 구현 * feat: REQUIREMENTS.md 업데이트 * refactor: 컨트롤러로 이동 * refactor: 프로그래밍 요구사항에 맞게 eslint 재설치 및 수정 * refactor: eslint max-params 규칙에 따라 수정 * refactor: eslint max-lines-per-function 규칙에 따라 LottoController 내의 메서드 분리 * refacotr: airbnb 컨벤션에 따라 isNan 대신 Number.isNan으로 수정 * refacotr: nullish coalescing operator(??) 사용으로 불필요한 비교 줄임 * refactor: eslint max-params 규칙에 따라 객체로 묶는 수정 * feat:winningNumbers와 bonusNumber를 배열로 반환하여 당첨 결과 출력 오류 수정 * refactor: eslint max-depth 규칙에 따라 메서드 분리 --------- Co-authored-by: unknown <[email protected]>
- Loading branch information
Showing
27 changed files
with
9,359 additions
and
3,935 deletions.
There are no files selected for viewing
This file contains 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,11 +1,79 @@ | ||
{ | ||
"rules": {}, | ||
"env": { | ||
"es6": true, | ||
"node": true | ||
"node": true, | ||
"jest": true | ||
}, | ||
"parserOptions": { | ||
"ecmaVersion": "latest" | ||
"ecmaVersion": "latest", | ||
"sourceType": "module" | ||
}, | ||
"extends": ["eslint:recommended", "plugin:prettier/recommended"] | ||
"extends": [ | ||
"airbnb", | ||
"airbnb/hooks", | ||
"prettier", | ||
"plugin:jest/recommended", | ||
"plugin:prettier/recommended", | ||
"plugin:import/errors", | ||
"plugin:import/warnings", | ||
"eslint:recommended" | ||
], | ||
"plugins": ["import", "jest", "prettier"], | ||
"rules": { | ||
"prettier/prettier": [ | ||
"error", | ||
{ | ||
"endOfLine": "auto", | ||
"printWidth": 100 | ||
} | ||
], | ||
"prefer-const": [ | ||
"error", | ||
{ | ||
"destructuring": "any", | ||
"ignoreReadBeforeAssign": false | ||
} | ||
], | ||
"max-depth": ["error", { "max": 1 }], | ||
"max-params": ["error", { "max": 2 }], | ||
"class-methods-use-this": "off", | ||
"max-lines-per-function": ["error", { "max": 10 }], | ||
"import/extensions": ["error", { "js": "ignorePackages" }], | ||
"import/order": [ | ||
"error", | ||
{ | ||
"groups": [ | ||
"builtin", | ||
"external", | ||
"internal", | ||
["parent", "sibling"], | ||
"index", | ||
"object", | ||
"type", | ||
"unknown" | ||
], | ||
"pathGroups": [ | ||
{ | ||
"pattern": "next", | ||
"group": "builtin", | ||
"position": "before" | ||
}, | ||
{ | ||
"pattern": "@/core/**", | ||
"group": "unknown" | ||
}, | ||
{ | ||
"pattern": "**/*.css.ts", | ||
"group": "unknown", | ||
"position": "after" | ||
} | ||
], | ||
"newlines-between": "always", | ||
"alphabetize": { | ||
"order": "asc", | ||
"caseInsensitive": true | ||
} | ||
} | ||
] | ||
} | ||
} |
This file contains 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,3 +1,12 @@ | ||
{ | ||
"printWidth": 100, | ||
"tabWidth": 2, | ||
"useTabs": false, | ||
"semi": true, | ||
"singleQuote": true, | ||
"trailingComma": "none", | ||
"bracketSpacing": true, | ||
"arrowParens": "always", | ||
"proseWrap": "never", | ||
"endOfLine": "auto" | ||
} |
This file contains 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,3 +1,61 @@ | ||
<h1 align="middle">🎱</h1> | ||
<h2 align="middle">level1 - 행운의 로또</h2> | ||
<p align="middle">자바스크립트로 구현 하는 로또 어플리케이션</p> | ||
|
||
### 실행 | ||
|
||
```bash | ||
npm run start-step1 | ||
``` | ||
|
||
### 테스트 | ||
|
||
```bash | ||
npm run test | ||
``` | ||
|
||
### 실행 화면 | ||
|
||
<img src="./lotto-step1.png" alt="실행화면" width="450"> | ||
|
||
### 테스트 실행 화면 | ||
|
||
<img src="./lotto-step1-test.png" alt="테스트 실행화면" width="300"> | ||
|
||
### 도메인 로직 | ||
|
||
### 파일 구조 | ||
|
||
``` | ||
📦src | ||
┣ 📂constant : 상수 관리 | ||
┃ ┣ 📜Messages.js : 입력, 입력힌트, 출력, 에러 메세지 관리 | ||
┃ ┗ 📜Options.js : 로또 가격, 로또 범위 최소, 최대 숫자, 상금, 게임 결과 등 관리 | ||
┣ 📂controller | ||
┃ ┗ 📜LottoController.js : 로또 게임 진행 | ||
┣ 📂domain | ||
┃ ┣ 📜Lotto.js | ||
┃ ┗ 📜LottoMachine.js | ||
┣ 📂util | ||
┃ ┣ 📂random | ||
┃ ┃ ┗ 📜Random.js | ||
┃ ┣ 📂readLine | ||
┃ ┃ ┗ 📜readLineAsync.js | ||
┃ ┗ 📂validation | ||
┃ ┃ ┣ 📜LottoNumbersValidator.js | ||
┃ ┃ ┣ 📜PurchaseAmountValidator.js | ||
┃ ┃ ┗ 📜Validation.js | ||
┣ 📂view | ||
┃ ┣ 📜InputView.js | ||
┃ ┗ 📜OutputView.js | ||
┣ 📜App.js | ||
┣ 📜step1-index.js | ||
┗ 📜step2-index.js | ||
📦__tests__ | ||
┣ 📜.gitkeep | ||
┣ 📜Lotto.test.js | ||
┣ 📜LottoMachine.test.js | ||
┗ 📜Random.test.js | ||
``` |
This file contains 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,102 @@ | ||
/* eslint-disable max-lines-per-function */ | ||
import { VARIABLE_ALIAS, ERROR_MESSAGES } from '../src/constant/Messages.js'; | ||
import OPTIONS from '../src/constant/Options.js'; | ||
import Lotto from '../src/domain/Lotto.js'; | ||
|
||
describe('Lotto 단위테스트', () => { | ||
describe('로또 발행 테스트', () => { | ||
test.each([[[1, 2, 3, 4, 5]], [[1, 2, 3, 4, 5, 6, 7]]])( | ||
'로또 번호(%s)가 6개가 아니면 에러를 발생한다.', | ||
(numbers) => { | ||
expect(() => new Lotto(numbers)).toThrow( | ||
`${ERROR_MESSAGES.prefix}${ERROR_MESSAGES.hasNotLength(VARIABLE_ALIAS.lottoNumbers, OPTIONS.LOTTO.combination)}` | ||
); | ||
} | ||
); | ||
|
||
test.each([[['a', 2, 3, 4, 5, 6]], [[1.1, 2, 3, 4, 5, 6]]])( | ||
'로또 번호(%s) 중 정수 이외의 값이 있다면 에러를 발생한다.', | ||
(numbers) => { | ||
expect(() => new Lotto(numbers)).toThrow(); | ||
} | ||
); | ||
|
||
test.each([[[1, 2, 3, 4, 5, 46]], [[0, 1, 2, 3, 4, 5]]])( | ||
'로또 번호(%s)의 범위가 1부터 45 사이가 아니라면 에러를 발생한다.', | ||
(numbers) => { | ||
expect(() => new Lotto(numbers)).toThrow(); | ||
} | ||
); | ||
|
||
test('로또 번호에 중복되는 수가 있다면 에러를 발생한다.', () => { | ||
const numbers = [1, 1, 2, 3, 4, 5]; | ||
|
||
expect(() => new Lotto(numbers)).toThrow(); | ||
}); | ||
|
||
test.each([[[3, 2, 4, 5, 6, 1]], [[13, 12, 14, 15, 16, 11]]])( | ||
'로또 번호는 오름차순으로 정렬된다.', | ||
(numbers) => { | ||
const lotto = new Lotto(numbers); | ||
|
||
expect(lotto.getNumbers()).toStrictEqual(numbers.sort()); | ||
} | ||
); | ||
}); | ||
|
||
describe('로또 당첨 여부 판단 테스트', () => { | ||
let lottoNumbers; | ||
let lotto; | ||
|
||
beforeAll(() => { | ||
lottoNumbers = [1, 2, 3, 4, 5, 6]; | ||
lotto = new Lotto(lottoNumbers); | ||
}); | ||
|
||
test.each([[[1, 2, 3, 4, 5, 6], 7, 1]])( | ||
'6개의 번호가 일치하면 1등을 반환한다.', | ||
(winningNumbers, bonusNumber, rank) => { | ||
expect(lotto.determineRank(winningNumbers, bonusNumber)).toBe(rank); | ||
} | ||
); | ||
|
||
test.each([[[1, 2, 3, 4, 5, 45], 6, 2]])( | ||
'5개의 번호 + 보너스 번호가 일치하면 2등을 반환한다.', | ||
(winningNumbers, bonusNumber, rank) => { | ||
expect(lotto.determineRank(winningNumbers, bonusNumber)).toBe(rank); | ||
} | ||
); | ||
|
||
test.each([[[1, 2, 3, 4, 5, 45], 7, 3]])( | ||
'5개의 번호가 일치하고 보너스 번호가 다르면 3등을 반환한다.', | ||
(winningNumbers, bonusNumber, rank) => { | ||
expect(lotto.determineRank(winningNumbers, bonusNumber)).toBe(rank); | ||
} | ||
); | ||
|
||
test.each([ | ||
[[1, 2, 3, 4, 44, 45], 6, 4], | ||
[[1, 2, 3, 4, 44, 45], 7, 4] | ||
])('4개의 번호가 일치하면 4등을 반환한다.', (winningNumbers, bonusNumber, rank) => { | ||
expect(lotto.determineRank(winningNumbers, bonusNumber)).toBe(rank); | ||
}); | ||
|
||
test.each([ | ||
[[1, 2, 3, 43, 44, 45], 6, 5], | ||
[[1, 2, 3, 43, 44, 45], 7, 5] | ||
])('3개의 번호가 일치하면 5등을 반환한다.', (winningNumbers, bonusNumber, rank) => { | ||
expect(lotto.determineRank(winningNumbers, bonusNumber)).toBe(rank); | ||
}); | ||
|
||
test.each([ | ||
[[1, 2, 42, 43, 44, 45], 6, 6], | ||
[[1, 2, 42, 43, 44, 45], 7, 6], | ||
[[1, 41, 42, 43, 44, 45], 6, 6], | ||
[[1, 41, 42, 43, 44, 45], 7, 6], | ||
[[40, 41, 42, 43, 44, 45], 6, 6], | ||
[[40, 41, 42, 43, 44, 45], 7, 6] | ||
])('3개 미만의 번호가 일치하면 다음기회에^^.', (winningNumbers, bonusNumber, rank) => { | ||
expect(lotto.determineRank(winningNumbers, bonusNumber)).toBe(rank); | ||
}); | ||
}); | ||
}); |
This file contains 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,88 @@ | ||
/* eslint-disable max-lines-per-function */ | ||
import { ERROR_MESSAGES, VARIABLE_ALIAS } from '../src/constant/Messages.js'; | ||
import OPTIONS from '../src/constant/Options.js'; | ||
import Lotto from '../src/domain/Lotto.js'; | ||
import LottoMachine from '../src/domain/LottoMachine'; | ||
|
||
// TODO: calculateIssueQuantity 함수를 private 함수로 변경 | ||
describe('LottoMachine 단위테스트', () => { | ||
let lottoMachine; | ||
|
||
beforeEach(() => { | ||
lottoMachine = new LottoMachine(); | ||
}); | ||
|
||
describe('구입 금액에 따른 구입 가능 수량 계산 테스트', () => { | ||
test.each(['a', 1.1])( | ||
'구입 금액(%s)이 정수가 아니라면 에러를 발생시킨다.', | ||
(purchaseAmount) => { | ||
expect(() => lottoMachine.calculateIssueQuantity(purchaseAmount)).toThrow( | ||
`${ERROR_MESSAGES.prefix}${ERROR_MESSAGES.isNotInteger(VARIABLE_ALIAS.purchaseAmount)}` | ||
); | ||
} | ||
); | ||
|
||
test('구입 금액이 1000 미만이라면 에러를 발생시킨다.', () => { | ||
const purchaseAmount = 999; | ||
|
||
expect(() => lottoMachine.calculateIssueQuantity(purchaseAmount)).toThrow( | ||
`${ERROR_MESSAGES.prefix}${ERROR_MESSAGES.isNotAtLeast(VARIABLE_ALIAS.purchaseAmount, OPTIONS.LOTTO.price)}` | ||
); | ||
}); | ||
|
||
test.each([ | ||
[1000, 1], | ||
[1999, 1], | ||
[2000, 2] | ||
])('구입 금액(%s)에 따른 발행 수량(%s)을 계산하여 반환한다.', (purchaseAmount, quantity) => { | ||
expect(lottoMachine.calculateIssueQuantity(purchaseAmount)).toBe(quantity); | ||
}); | ||
}); | ||
|
||
describe('지정 수량 만큼 로또 발행 테스트', () => { | ||
test('지정 수량만큼 로또를 발행한다.', () => { | ||
const issueQuantity = 1; | ||
const lottos = lottoMachine.issueLottos(issueQuantity); | ||
|
||
expect(Array.isArray(lottos) && lottos.length === issueQuantity).toBe(true); | ||
}); | ||
|
||
test('로또 객체의 배열을 반환한다.', () => { | ||
const issueQuantity = 1; | ||
const lottos = lottoMachine.issueLottos(issueQuantity); | ||
|
||
expect(lottos.every((lotto) => lotto instanceof Lotto)).toBe(true); | ||
}); | ||
}); | ||
|
||
describe('로또 결과 분석 테스트', () => { | ||
// TEST: 1. 등수 별 당첨 개수 | ||
test('구입한 로또들의 등수를 분석하여 반환한다.', () => { | ||
const numbersList = [ | ||
[1, 2, 3, 4, 5, 6], | ||
[1, 2, 3, 4, 5, 7], | ||
[1, 2, 3, 4, 5, 45], | ||
[1, 2, 3, 4, 44, 45], | ||
[1, 2, 3, 43, 44, 45], | ||
[1, 2, 42, 43, 44, 45] | ||
]; | ||
const lottos = numbersList.map((numbers) => new Lotto(numbers)); | ||
const winningNumbers = [1, 2, 3, 4, 5, 6]; | ||
const bonusNumber = 7; | ||
|
||
const winningResult = { 1: 1, 2: 1, 3: 1, 4: 1, 5: 1, 6: 1 }; | ||
|
||
expect(lottoMachine.determineLottoRanks(lottos, winningNumbers, bonusNumber)).toStrictEqual( | ||
winningResult | ||
); | ||
}); | ||
|
||
// TEST: 2. 수익률 계산 | ||
test.each([[{ 1: 0, 2: 0, 3: 0, 4: 0, 5: 1, 6: 7 }, 62.5]])( | ||
'로또 결과에 따른 수익률을 반환한다.', | ||
(winningResult, profitRate) => { | ||
expect(lottoMachine.calculateProfitRate(winningResult)).toBe(profitRate); | ||
} | ||
); | ||
}); | ||
}); |
This file contains 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,33 @@ | ||
/* eslint-disable max-lines-per-function */ | ||
import Random from '../src/util/random/Random.js'; | ||
|
||
describe('랜덤 테스트', () => { | ||
test('특정 범위 내에서 랜덤 값 1개를 반환한다.', () => { | ||
const min = 1; | ||
const max = 10; | ||
const testCount = 10; | ||
|
||
const testSuites = Array(testCount) | ||
.fill() | ||
.map(() => Random.randomPickNumber(min, max)); | ||
|
||
expect(testSuites.every((testSuite) => min <= testSuite && testSuite <= max)).toBe(true); | ||
}); | ||
|
||
test('특정 범위 내에서 중복되지 않은 조합을 반환한다.', () => { | ||
const min = 1; | ||
const max = 45; | ||
const count = 6; | ||
const testCount = 5; | ||
|
||
const testSuites = Array(testCount) | ||
.fill() | ||
.map(() => Random.pickCombination(min, max, count)); | ||
|
||
expect( | ||
testSuites.every((testSuite) => { | ||
return testSuite.length === new Set(testSuite).size && testSuite.length === count; | ||
}) | ||
).toBe(true); | ||
}); | ||
}); |
Oops, something went wrong.