diff --git a/README.md b/README.md index 15bb106b5..2b355df85 100644 --- a/README.md +++ b/README.md @@ -1 +1,47 @@ # javascript-lotto-precourse + +## 구현할 기능 목록 + +- 로또 구입 금액 입력받기 + +- 로또 구입 갯수 출력하기 + +- 로또 구입 내역 출력하기 + - 발행한 로또 번호는 오름차순으로 정렬 + - 줄바꿈을 통해 로또 1개씩 출력 + +- 당첨 번호 입력 받기 + - 쉼표(,)를 기준으로 1~45 범위의 6개의 중복되지 않은 숫자를 입력받기 + +- 보너스 번호 입력 받기 + +- 당첨 통계 출력하기 + - 당첨 번호를 구분자 쉼표(,)를 기준으로 구분 + - 로또, 당첨 번호, 보너스 번호를 가지고 해당 로또가 맞힌 갯수와 보너스 번호를 맞힌 여부를 반환하는 메서드 (개별 로또 결과 계산) + - 모든 로또에 대해 위의 결과를 계산, 맞힌 갯수가 3,4,5,5+보너스,6일 경우 각각 카운트 저장 + - 수익률 계산 + - 당첨내역 출력하기 + - 수익률 출력하기 (소수점 둘째자리에서 반올림) + +- 리팩토링 + - App에 있는 로또 관련 로직 로또 클래스에 옮기기 + - App에 있는 입출력 관련 로직 view로 분리하기 + +- 에러 처리 + - 로또 클래스 관련 + - 로또 번호 6개가 아닌 경우(이미 구현되어 있음) + - 로또 번호가 중복되는 경우 + - 로또 번호가 1~45 범위를 벗어나는 경우 + - 로또 구매 가격 입력 관련 + - 입력받은 금액이 숫자가 아닌 경우 + - 입력받은 금액이 1,000원 미만일 경우 + - 입력받은 금액이 1,000원 단위가 아닌 경우 + - 당첨 번호 관련 + - 당첨 번호가 숫자가 아닌 경우 + - 당첨 번호가 1~45 범위를 벗어나는 경우 + - 당첨 번호에 중복된 숫자가 있는 경우 + - 당첨 번호 개수가 6개가 아닌 경우 + - 보너스 번호 관련 + - 보너스 번호가 숫자가 아닌 경우 + - 보너스 번호를 입력받을 때 1~45 범위를 벗어나는 경우 -> 에러 처리 + - 보너스 번호를 입력받을 때 당첨 번호와 중복되는 경우 -> 에러 처리 diff --git a/__tests__/ApplicationTest.js b/__tests__/ApplicationTest.js index 872380c9c..b8a446383 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 App from '../src/App.js'; +import { MissionUtils } from '@woowacourse/mission-utils'; const mockQuestions = (inputs) => { MissionUtils.Console.readLineAsync = jest.fn(); @@ -19,7 +19,7 @@ const mockRandoms = (numbers) => { }; const getLogSpy = () => { - const logSpy = jest.spyOn(MissionUtils.Console, "print"); + const logSpy = jest.spyOn(MissionUtils.Console, 'print'); logSpy.mockClear(); return logSpy; }; @@ -29,7 +29,7 @@ const runException = async (input) => { const logSpy = getLogSpy(); const RANDOM_NUMBERS_TO_END = [1, 2, 3, 4, 5, 6]; - const INPUT_NUMBERS_TO_END = ["1000", "1,2,3,4,5,6", "7"]; + const INPUT_NUMBERS_TO_END = ['1000', '1,2,3,4,5,6', '7']; mockRandoms([RANDOM_NUMBERS_TO_END]); mockQuestions([input, ...INPUT_NUMBERS_TO_END]); @@ -39,15 +39,15 @@ const runException = async (input) => { await app.run(); // then - expect(logSpy).toHaveBeenCalledWith(expect.stringContaining("[ERROR]")); + expect(logSpy).toHaveBeenCalledWith(expect.stringContaining('[ERROR]')); }; -describe("로또 테스트", () => { +describe('로또 테스트', () => { beforeEach(() => { jest.restoreAllMocks(); }); - test("기능 테스트", async () => { + test('기능 테스트', async () => { // given const logSpy = getLogSpy(); @@ -61,7 +61,7 @@ describe("로또 테스트", () => { [2, 13, 22, 32, 38, 45], [1, 3, 5, 14, 22, 45], ]); - mockQuestions(["8000", "1,2,3,4,5,6", "7"]); + mockQuestions(['8000', '1,2,3,4,5,6', '7']); // when const app = new App(); @@ -69,21 +69,21 @@ describe("로또 테스트", () => { // then const logs = [ - "8개를 구매했습니다.", - "[8, 21, 23, 41, 42, 43]", - "[3, 5, 11, 16, 32, 38]", - "[7, 11, 16, 35, 36, 44]", - "[1, 8, 11, 31, 41, 42]", - "[13, 14, 16, 38, 42, 45]", - "[7, 11, 30, 40, 42, 43]", - "[2, 13, 22, 32, 38, 45]", - "[1, 3, 5, 14, 22, 45]", - "3개 일치 (5,000원) - 1개", - "4개 일치 (50,000원) - 0개", - "5개 일치 (1,500,000원) - 0개", - "5개 일치, 보너스 볼 일치 (30,000,000원) - 0개", - "6개 일치 (2,000,000,000원) - 0개", - "총 수익률은 62.5%입니다.", + '8개를 구매했습니다.', + '[8, 21, 23, 41, 42, 43]', + '[3, 5, 11, 16, 32, 38]', + '[7, 11, 16, 35, 36, 44]', + '[1, 8, 11, 31, 41, 42]', + '[13, 14, 16, 38, 42, 45]', + '[7, 11, 30, 40, 42, 43]', + '[2, 13, 22, 32, 38, 45]', + '[1, 3, 5, 14, 22, 45]', + '3개 일치 (5,000원) - 1개', + '4개 일치 (50,000원) - 0개', + '5개 일치 (1,500,000원) - 0개', + '5개 일치, 보너스 볼 일치 (30,000,000원) - 0개', + '6개 일치 (2,000,000,000원) - 0개', + '총 수익률은 62.5%입니다.', ]; logs.forEach((log) => { @@ -91,7 +91,7 @@ describe("로또 테스트", () => { }); }); - test("예외 테스트", async () => { - await runException("1000j"); + test('예외 테스트', async () => { + await runException('1000j'); }); }); diff --git a/__tests__/LottoResultTest.js b/__tests__/LottoResultTest.js new file mode 100644 index 000000000..e5222debf --- /dev/null +++ b/__tests__/LottoResultTest.js @@ -0,0 +1,42 @@ +import Lotto from '../src/lotto/Lotto.js'; +import LottoResult from '../src/lotto/LottoResult.js'; + +describe('LottoResult 단위 테스트', () => { + let lottoResult; + beforeEach(() => { + lottoResult = new LottoResult(); + }); + + test('당첨 통계 업데이트 및 총 상금 계산', () => { + const lottos = [ + new Lotto([1, 2, 3, 4, 5, 6]), // match6 + new Lotto([1, 2, 3, 4, 5, 7]), // match5 + bonus + new Lotto([1, 2, 3, 4, 8, 9]), // match4 + new Lotto([1, 2, 3, 10, 11, 12]), // match3 + new Lotto([13, 14, 15, 16, 17, 18]), // 0개 + ]; + + const winningNumbers = [1, 2, 3, 4, 5, 6]; + const bonusNumber = 7; + + lottoResult.updateWinningStatistics(lottos, winningNumbers, bonusNumber); + + const winningRank = lottoResult.getWinningRank(); + const totalPrize = lottoResult.getTotalPrize(); + const profitRate = lottoResult.calculateProfitRate(5000); + + // 맞은 개수 검증 + expect(winningRank.match3).toBe(1); + expect(winningRank.match4).toBe(1); + expect(winningRank.match5).toBe(0); + expect(winningRank.match5AndBonus).toBe(1); + expect(winningRank.match6).toBe(1); + + // 총 상금 검증 + const expectedTotal = 5000 + 50000 + 30000000 + 2000000000; + expect(totalPrize).toBe(expectedTotal); + + // 수익률 검증 + expect(profitRate).toBe(((expectedTotal / 5000) * 100).toFixed(1)); + }); +}); diff --git a/__tests__/LottoTest.js b/__tests__/LottoTest.js index 409aaf69b..22681ec00 100644 --- a/__tests__/LottoTest.js +++ b/__tests__/LottoTest.js @@ -1,18 +1,31 @@ -import Lotto from "../src/Lotto"; +import Lotto from '../src/lotto/Lotto'; -describe("로또 클래스 테스트", () => { - test("로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.", () => { +describe('로또 클래스 테스트', () => { + test('로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.', () => { expect(() => { new Lotto([1, 2, 3, 4, 5, 6, 7]); - }).toThrow("[ERROR]"); + }).toThrow('[ERROR]'); }); // TODO: 테스트가 통과하도록 프로덕션 코드 구현 - test("로또 번호에 중복된 숫자가 있으면 예외가 발생한다.", () => { + test('로또 번호에 중복된 숫자가 있으면 예외가 발생한다.', () => { expect(() => { new Lotto([1, 2, 3, 4, 5, 5]); - }).toThrow("[ERROR]"); + }).toThrow('[ERROR]'); }); // TODO: 추가 기능 구현에 따른 테스트 코드 작성 + test('로또 번호가 1~45 범위를 벗어나면 예외가 발생한다.', () => { + expect(() => { + new Lotto([0, 2, 3, 4, 5, 6]); + }).toThrow('[ERROR]'); + expect(() => { + new Lotto([1, 2, 3, 4, 5, 46]); + }).toThrow('[ERROR]'); + }); + + test('로또 번호가 정상적으로 생성되고 오름차순 정렬된다', () => { + const lotto = new Lotto([19, 3, 8, 21, 16, 31]); + expect(lotto.getNumbers()).toEqual([3, 8, 16, 19, 21, 31]); + }); }); diff --git a/__tests__/LottoUtilsTest.js b/__tests__/LottoUtilsTest.js new file mode 100644 index 000000000..848e82488 --- /dev/null +++ b/__tests__/LottoUtilsTest.js @@ -0,0 +1,8 @@ +import LottoUtils from '../src/lotto/LottoUtils.js'; + +describe('LottoUtils 단위 테스트', () => { + test('구매 금액에 따라 로또 개수 계산', () => { + expect(LottoUtils.getLottoCount(1000)).toBe(1); + expect(LottoUtils.getLottoCount(8000)).toBe(8); + }); +}); diff --git a/src/App.js b/src/App.js index 091aa0a5d..66c860693 100644 --- a/src/App.js +++ b/src/App.js @@ -1,5 +1,42 @@ +import Lotto from './lotto/Lotto.js'; +import LottoResult from './lotto/LottoResult.js'; +import LottoUtils from './lotto/LottoUtils.js'; +import InputHandler from './view/InputHandler.js'; +import OutputHandler from './view/OutputHandler.js'; + class App { - async run() {} + async run() { + // 1. 구매 금액 입력 + const purchaseInput = await InputHandler.readPurchaseAmount(); + + // 2. 로또 생성 + const lottoCount = LottoUtils.getLottoCount(purchaseInput); + const lottos = Lotto.generateLottos(lottoCount); + + // 3. 로또 결과 출력 + OutputHandler.printLottoCount(lottoCount); + OutputHandler.printLotto(lottos); + + // 4. 당첨 번호 입력 + const winnerLottoNumbers = await InputHandler.readWinningNumbers(); + const bonusLottoNumber = + await InputHandler.readBonusNumber(winnerLottoNumbers); + + // 5. 당첨 통계 계산 + const lottoResult = new LottoResult(); + lottoResult.updateWinningStatistics( + lottos, + winnerLottoNumbers, + bonusLottoNumber + ); + + // 6. 통계 출력 + const totalPrize = lottoResult.getTotalPrize(); + const profitRate = lottoResult.calculateProfitRate(purchaseInput); + const winningRank = lottoResult.getWinningRank(); + + OutputHandler.printStatistics(profitRate, winningRank); + } } export default App; diff --git a/src/Lotto.js b/src/Lotto.js deleted file mode 100644 index cb0b1527e..000000000 --- a/src/Lotto.js +++ /dev/null @@ -1,18 +0,0 @@ -class Lotto { - #numbers; - - constructor(numbers) { - this.#validate(numbers); - this.#numbers = numbers; - } - - #validate(numbers) { - if (numbers.length !== 6) { - throw new Error("[ERROR] 로또 번호는 6개여야 합니다."); - } - } - - // TODO: 추가 기능 구현 -} - -export default Lotto; diff --git a/src/constants/constants.js b/src/constants/constants.js new file mode 100644 index 000000000..273d0fbbe --- /dev/null +++ b/src/constants/constants.js @@ -0,0 +1,51 @@ +export const LOTTO = { + NUMBER_COUNT: 6, + MIN_NUMBER: 1, + MAX_NUMBER: 45, + MIN_PURCHASE_AMOUNT: 1000, + UNIT: 1000, +}; + +export const PRIZE = { + MATCH3: 5000, + MATCH4: 50000, + MATCH5: 1500000, + MATCH5_BONUS: 30000000, + MATCH6: 2000000000, +}; + +export const MESSAGES = { + PURCHASE_AMOUNT_PROMPT: '구매 금액을 입력해 주세요.\n', + WINNING_NUMBERS_PROMPT: '\n당첨 번호를 입력해 주세요.\n', + BONUS_NUMBER_PROMPT: '\n보너스 번호를 입력해 주세요.\n', +}; + +export const ERROR_MESSAGES = { + PURCHASE_NUMBER: '[ERROR] 구매 금액은 숫자로 입력해 주세요.', + MIN_PURCHASE: '[ERROR] 구매 금액은 1,000원 이상이어야 합니다.', + UNIT_PURCHASE: '[ERROR] 구매 금액은 1,000원 단위로 입력해 주세요.', + LOTTO_NUMBER_COUNT: '[ERROR] 로또 번호는 6개여야 합니다.', + LOTTO_NUMBER_DUPLICATE: '[ERROR] 로또 번호는 중복될 수 없습니다.', + LOTTO_NUMBER_RANGE: '[ERROR] 로또 번호는 1~45 범위여야 합니다.', + WINNING_NUMBER_FORMAT: '[ERROR] 당첨 번호는 숫자로 입력해 주세요.', + WINNING_NUMBER_RANGE: '[ERROR] 당첨 번호는 1 ~ 45 범위로 입력해 주세요.', + WINNING_NUMBER_COUNT: '[ERROR] 당첨 번호는 6개여야 합니다.', + WINNING_NUMBER_DUPLICATE: '[ERROR] 당첨 번호는 중복될 수 없습니다.', + BONUS_NUMBER_FORMAT: '[ERROR] 보너스 번호는 숫자로 입력해 주세요.', + BONUS_NUMBER_RANGE: '[ERROR] 보너스 번호는 1 ~ 45 범위로 입력해 주세요.', + BONUS_NUMBER_DUPLICATE: + '[ERROR] 보너스 번호는 당첨 번호와 중복될 수 없습니다.', +}; + +export const OUTPUT_MESSAGES = { + PURCHASED_COUNT: (count) => `\n${count}개를 구매했습니다.`, + STATISTICS_HEADER: '\n당첨 통계', + STATISTICS_SEPARATOR: '---', + STATISTICS_MATCH3: (count) => `3개 일치 (5,000원) - ${count}개`, + STATISTICS_MATCH4: (count) => `4개 일치 (50,000원) - ${count}개`, + STATISTICS_MATCH5: (count) => `5개 일치 (1,500,000원) - ${count}개`, + STATISTICS_MATCH5_BONUS: (count) => + `5개 일치, 보너스 볼 일치 (30,000,000원) - ${count}개`, + STATISTICS_MATCH6: (count) => `6개 일치 (2,000,000,000원) - ${count}개`, + PROFIT_RATE: (rate) => `총 수익률은 ${rate}%입니다.`, +}; diff --git a/src/index.js b/src/index.js index 02a1d389e..9f1977d76 100644 --- a/src/index.js +++ b/src/index.js @@ -1,4 +1,4 @@ -import App from "./App.js"; +import App from './controller/App.js'; const app = new App(); await app.run(); diff --git a/src/lotto/Lotto.js b/src/lotto/Lotto.js new file mode 100644 index 000000000..551d0d5da --- /dev/null +++ b/src/lotto/Lotto.js @@ -0,0 +1,56 @@ +import { Random } from '@woowacourse/mission-utils'; +import { LOTTO, ERROR_MESSAGES } from '../constants/constants.js'; + +class Lotto { + #numbers; + + constructor(numbers) { + this.#validate(numbers); + this.#numbers = numbers.sort((a, b) => a - b); + } + + #validate(numbers) { + if (numbers.length !== 6) { + throw new Error(ERROR_MESSAGES.LOTTO_NUMBER_COUNT); + } + + const uniqueNumbers = new Set(numbers); + if (uniqueNumbers.size !== numbers.length) + throw new Error(ERROR_MESSAGES.LOTTO_NUMBER_DUPLICATE); + + for (let i = 0; i < numbers.length; i++) { + if (numbers[i] < LOTTO.MIN_NUMBER || numbers[i] > LOTTO.MAX_NUMBER) + throw new Error(ERROR_MESSAGES.LOTTO_NUMBER_RANGE); + } + } + + static generateLottos(lottoCount) { + const result = []; + + for (let i = 0; i < lottoCount; i++) { + const numbers = Random.pickUniqueNumbersInRange( + LOTTO.MIN_NUMBER, + LOTTO.MAX_NUMBER, + LOTTO.NUMBER_COUNT + ); + result.push(new Lotto(numbers)); + } + + return result; + } + + getNumbers() { + return this.#numbers; + } + + compareWithWinningNumbers(winningNumbers, bonusNumber) { + const matchCount = this.#numbers.filter((n) => + winningNumbers.includes(n) + ).length; + const hasBonus = this.#numbers.includes(bonusNumber); + + return { matchCount, hasBonus }; + } +} + +export default Lotto; diff --git a/src/lotto/LottoResult.js b/src/lotto/LottoResult.js new file mode 100644 index 000000000..5c912e877 --- /dev/null +++ b/src/lotto/LottoResult.js @@ -0,0 +1,61 @@ +import { PRIZE } from '../constants/constants.js'; + +class LottoResult { + #winningRank = { + match3: 0, + match4: 0, + match5: 0, + match5AndBonus: 0, + match6: 0, + }; + + #prizeTable = { + 1: PRIZE.MATCH6, + 2: PRIZE.MATCH5_BONUS, + 3: PRIZE.MATCH5, + 4: PRIZE.MATCH4, + 5: PRIZE.MATCH3, + }; + + getWinningRank() { + return { ...this.#winningRank }; + } + + updateWinningStatistics(lottos, winningNumbers, bonusNumber) { + for (const lotto of lottos) { + const { matchCount, hasBonus } = lotto.compareWithWinningNumbers( + winningNumbers, + bonusNumber + ); + + if (matchCount === 3) this.#winningRank.match3++; + if (matchCount === 4) this.#winningRank.match4++; + if (matchCount === 5 && !hasBonus) this.#winningRank.match5++; + if (matchCount === 5 && hasBonus) this.#winningRank.match5AndBonus++; + if (matchCount === 6) this.#winningRank.match6++; + } + } + + getTotalPrize() { + const prizeMap = { + match3: this.#prizeTable[5], + match4: this.#prizeTable[4], + match5: this.#prizeTable[3], + match5AndBonus: this.#prizeTable[2], + match6: this.#prizeTable[1], + }; + + return Object.entries(this.#winningRank).reduce( + (sum, [rank, count]) => sum + (prizeMap[rank] ?? 0) * count, + 0 + ); + } + + calculateProfitRate(purchaseAmount) { + const totalPrize = this.getTotalPrize(); + const profitRate = (totalPrize / purchaseAmount) * 100; + return profitRate.toFixed(1); + } +} + +export default LottoResult; diff --git a/src/lotto/LottoUtils.js b/src/lotto/LottoUtils.js new file mode 100644 index 000000000..7cf7cc90f --- /dev/null +++ b/src/lotto/LottoUtils.js @@ -0,0 +1,7 @@ +class LottoUtils { + static getLottoCount(purchaseAmount) { + return Number(purchaseAmount) / 1000; + } +} + +export default LottoUtils; diff --git a/src/view/InputHandler.js b/src/view/InputHandler.js new file mode 100644 index 000000000..8c14a9d09 --- /dev/null +++ b/src/view/InputHandler.js @@ -0,0 +1,78 @@ +import { Console } from '@woowacourse/mission-utils'; +import { MESSAGES, ERROR_MESSAGES } from '../constants/constants.js'; + +class InputHandler { + static async readInputWithValidation(message, validateFn) { + while (true) { + try { + const input = await Console.readLineAsync(message); + validateFn(input); + return input; + } catch (error) { + Console.print(error.message); + } + } + } + + static async readPurchaseAmount() { + const input = await this.readInputWithValidation( + MESSAGES.PURCHASE_AMOUNT_PROMPT, + this.validatePurchaseAmount + ); + + return Number(input); + } + + static async readWinningNumbers() { + const input = await this.readInputWithValidation( + MESSAGES.WINNING_NUMBERS_PROMPT, + (raw) => this.validateWinningNumbers(this.parseWinningNumbers(raw)) + ); + + return this.parseWinningNumbers(input); + } + + static async readBonusNumber(winningNumbers) { + const input = await this.readInputWithValidation( + MESSAGES.BONUS_NUMBER_PROMPT, + (raw) => this.validateBonusNumber(Number(raw), winningNumbers) + ); + + return Number(input); + } + + static parseWinningNumbers(winningNumbers) { + return winningNumbers.split(',').map((s) => Number(s)); + } + + static validatePurchaseAmount(purchaseAmount) { + const money = Number(purchaseAmount); + if (isNaN(money)) throw new Error(ERROR_MESSAGES.PURCHASE_NUMBER); + if (money < 1000) throw new Error(ERROR_MESSAGES.MIN_PURCHASE); + if (money % 1000 !== 0) throw new Error(ERROR_MESSAGES.UNIT_PURCHASE); + } + + static validateWinningNumbers(numbers) { + numbers.forEach((number) => { + if (isNaN(number)) throw new Error(ERROR_MESSAGES.WINNING_NUMBER_FORMAT); + if (number < 1 || number > 45) + throw new Error(ERROR_MESSAGES.WINNING_NUMBER_RANGE); + }); + + if (numbers.length !== 6) + throw new Error(ERROR_MESSAGES.WINNING_NUMBER_COUNT); + const unique = new Set(numbers); + if (unique.size !== numbers.length) + throw new Error(ERROR_MESSAGES.WINNING_NUMBER_DUPLICATE); + } + + static validateBonusNumber(bonusNumber, winningNumbers) { + if (isNaN(bonusNumber)) throw new Error(ERROR_MESSAGES.BONUS_NUMBER_FORMAT); + if (bonusNumber < 1 || bonusNumber > 45) + throw new Error(ERROR_MESSAGES.BONUS_NUMBER_RANGE); + if (winningNumbers.includes(bonusNumber)) + throw new Error(ERROR_MESSAGES.BONUS_NUMBER_DUPLICATE); + } +} + +export default InputHandler; diff --git a/src/view/OutputHandler.js b/src/view/OutputHandler.js new file mode 100644 index 000000000..ecb2c59d6 --- /dev/null +++ b/src/view/OutputHandler.js @@ -0,0 +1,29 @@ +import { Console } from '@woowacourse/mission-utils'; +import { OUTPUT_MESSAGES } from '../constants/constants.js'; + +class OutputHandler { + static printLottoCount(lottoCount) { + Console.print(OUTPUT_MESSAGES.PURCHASED_COUNT(lottoCount)); + } + + static printLotto(lottos) { + lottos.forEach((lotto) => + Console.print(`[${lotto.getNumbers().join(', ')}]`) + ); + } + + static printStatistics(profitRate, winningRank) { + Console.print(OUTPUT_MESSAGES.STATISTICS_HEADER); + Console.print(OUTPUT_MESSAGES.STATISTICS_SEPARATOR); + Console.print(OUTPUT_MESSAGES.STATISTICS_MATCH3(winningRank.match3)); + Console.print(OUTPUT_MESSAGES.STATISTICS_MATCH4(winningRank.match4)); + Console.print(OUTPUT_MESSAGES.STATISTICS_MATCH5(winningRank.match5)); + Console.print( + OUTPUT_MESSAGES.STATISTICS_MATCH5_BONUS(winningRank.match5AndBonus) + ); + Console.print(OUTPUT_MESSAGES.STATISTICS_MATCH6(winningRank.match6)); + Console.print(OUTPUT_MESSAGES.PROFIT_RATE(profitRate)); + } +} + +export default OutputHandler;