Skip to content
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

[로또 게임 미션] 이은학 제출합니다. #648

Open
wants to merge 19 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 79 additions & 2 deletions __tests__/LottoTest.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import Lotto from "../src/Lotto.js";
import App from "../src/App.js";

describe("로또 클래스 테스트", () => {
test("로또 번호의 개수가 6개가 넘어가면 예외가 발생한다.", () => {
Expand All @@ -7,12 +8,88 @@ describe("로또 클래스 테스트", () => {
}).toThrow("[ERROR]");
});

// TODO: 이 테스트가 통과할 수 있게 구현 코드 작성
test("로또 번호에 중복된 숫자가 있으면 예외가 발생한다.", () => {
expect(() => {
new Lotto([1, 2, 3, 4, 5, 5]);
}).toThrow("[ERROR]");
});

// 아래에 추가 테스트 작성 가능
test("로또 번호에 1보다 작은 숫자가 있으면 예외가 발생한다.", () => {
expect(() => {
new Lotto([1, 2, 3, 4, 5, 0]);
}).toThrow("[ERROR]");
});

test("로또 번호에 45보다 큰 숫자가 있으면 예외가 발생한다.", () => {
expect(() => {
new Lotto([1, 2, 3, 4, 5, 46]);
}).toThrow("[ERROR]");
});

test("hasNumber 테스트",()=>{
const lotto = new Lotto([1,2,3,4,5,6]);
expect(lotto.hasNumber(1)).toEqual(true);
expect(lotto.hasNumber(7)).toEqual(false);
})
});

describe("App.js 메서드 단위 테스트",()=>{
test("constructor 테스트",()=>{
const app = new App();
expect(app.winningNumbers).toEqual([]);
expect(app.bonusNumber).toEqual(0);
})
test("validateMoney 테스트",()=>{
const app = new App();
expect(app.validateMoney(1000)).toEqual(true);
expect(app.validateMoney(1001)).toEqual(undefined);
expect(app.validateMoney(0)).toEqual(undefined);
expect(app.validateMoney(-1)).toEqual(undefined);
})
test("publishLotto 테스트",()=>{
const app = new App();
const lottos = app.publishLotto(1000);
expect(lottos.length).toEqual(1);
expect(lottos[0].getNumbers().length).toEqual(6);
})
test("validateNumbers 테스트",()=>{
const app = new App();
expect(app.validateNumbers([1,2,3,4,5,6])).toEqual(true);
expect(app.validateNumbers([1,2,3,4,5,5])).toEqual(undefined);
expect(app.validateNumbers([1,2,3,4,5,0])).toEqual(undefined);
expect(app.validateNumbers([1,2,3,4,5,46])).toEqual(undefined);
})
test("validateBonusNumber 테스트",()=>{
const app = new App();
app.winningNumbers = [1,2,3,4,5,6];
expect(app.validateBonusNumber(7)).toEqual(true);
expect(app.validateBonusNumber(6)).toEqual(undefined);
expect(app.validateBonusNumber(0)).toEqual(undefined);
expect(app.validateBonusNumber(46)).toEqual(undefined);
})
test("countMatchingNumbers 테스트",()=>{
const app = new App();
app.winningNumbers = [1,2,3,4,5,6];
const lotto = new Lotto([1,2,3,4,5,6]);
expect(app.countMatchingNumbers(lotto)).toEqual(6);
app.winningNumbers = [1,2,3,4,5,7];
expect(app.countMatchingNumbers(lotto)).toEqual(5);
})
test("computeRank 테스트",()=>{
const app = new App();
app.bonusNumber = 7;
const lotto = new Lotto([1,2,3,4,5,6]);
app.winningNumbers = [1,2,3,4,5,6];
expect(app.computeRank(lotto)).toEqual("1등");
// app.winningNumbers = [1,2,3,4,5,7];
// expect(app.computeRank(lotto)).toEqual("2등");
app.winningNumbers = [1,2,3,4,5,8];
expect(app.computeRank(lotto)).toEqual("3등");
app.winningNumbers = [1,2,3,4,7,8];
expect(app.computeRank(lotto)).toEqual("4등");
app.winningNumbers = [1,2,3,7,8,9];
expect(app.computeRank(lotto)).toEqual("5등");
app.winningNumbers = [1,2,7,8,9,10];
expect(app.computeRank(lotto)).toEqual("꽝");
});
})
54 changes: 54 additions & 0 deletions docs/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
로또 게임 기능 목록
--------------------
<br>

- 로또 구입 금액 입력받기
- 1,000원으로 나누어 떨어지지 않는 경우 예외 처리한다
<br>

- 로또 발행
- 구입 금액에 해당하는 만큼 로또를 발행한다
- 1~45범위의 중복되지 않는 6개의 숫자를 뽑는다
- 로또 1장의 가격은 1000원이다
<br>

- 구매한 로또 수량 및 번호 출력
- 로또 번호는 오름차순으로 정렬하여 보여준다
<br>

- 당첨 번호를 입력 받는다
- 1~45범위의 중복되지 않는 6개의 숫자를 입력받는다
- 번호는 쉼표(,)를 기준으로 구분한다.
- 보너스 번호를 입력 받는다
- 보너스 번호는 당첨 번호와 중복될 수 없다
<br>

- 잘못된 입력에 대한 예외처리 (각 입력마다)
- 사용자가 잘못된 값을 입력할 경우 throw문을 사용해 예외를 발생시킨다. 그런 다음, "[ERROR]"로 시작하는 에러 메시지를 출력하고 해당 부분부터 입력을 다시 받는다.
<br>

- 로또 하나에 대한 결과(등수) 계산
- 당첨은 1등부터 5등까지 있다. 당첨 기준과 금액은 아래와 같다
- 5개가 일치할 시 틀린 1개가 보너스 번호와 일치하는지 확인하여 2등과 3등을 나눈다
1등: 6개 번호 일치 / 2,000,000,000원
2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원
3등: 5개 번호 일치 / 1,500,000원
4등: 4개 번호 일치 / 50,000원
5등: 3개 번호 일치 / 5,000원
<br>

- 총 당첨 내역 출력
- 각 로또의 결과를 합산한다
- 아래와 같은 형식으로 출력한다
3개 일치 (5,000원) - ?개
4개 일치 (50,000원) - ?개
5개 일치 (1,500,000원) - ?개
5개 일치, 보너스 볼 일치 (30,000,000원) - ?개
6개 일치 (2,000,000,000원) - ?개
<br>

- 수익률 계산
- 수익률은 소수점 둘째 자리에서 반올림한다
<br>

- 수익률 출력
180 changes: 179 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,183 @@
import { MissionUtils } from "@woowacourse/mission-utils";
import Lotto from "./Lotto.js";

class App {
async play() {}
constructor() {
this.winningNumbers = [];
this.bonusNumber = 0;
}

async inputMoney() {
let money;
let isValidated = false;
while (!isValidated) {
const inputString = await MissionUtils.Console.readLineAsync(
"구입금액을 입력해 주세요.\n"
);
money = Number(inputString.trim());
isValidated = this.validateMoney(money);
}
return money;
}

validateMoney(money) {
if (isNaN(money)) {
MissionUtils.Console.print("[ERROR] 숫자만 입력해주세요.\n");
}else if(money < 1000){
MissionUtils.Console.print("[ERROR] 최소 1000 이상을 입력해야 합니다.\n");
}
else if (money % 1000 !== 0) {
MissionUtils.Console.print("[ERROR] 1000원 단위로 입력해주세요.\n");
} else return true;
}

publishLotto(money) {
const lottoCount = money / 1000;
const lottos = [];
for (let i = 0; i < lottoCount; i++) {
const randomNumbers = MissionUtils.Random.pickUniqueNumbersInRange(
1,
45,
6
);
try {
const lotto = new Lotto(randomNumbers);
lottos.push(lotto);
} catch (error) {
MissionUtils.Console.print(error.message);
i--;
}
}
return lottos;
}

printLottos(lottos) {
MissionUtils.Console.print(`\n${lottos.length}개를 구매했습니다.`);
for (const lotto of lottos) {
MissionUtils.Console.print(lotto.toString());
}
}

async inputNumbers() {
let numbers;
let isValidated = false;
while (!isValidated) {
const inputString = await MissionUtils.Console.readLineAsync(
"\n당첨 번호를 입력해주세요.\n"
);
numbers = inputString.split(",").map((number) => Number(number.trim()));
isValidated = this.validateNumbers(numbers);
}
this.winningNumbers = numbers;
}

validateNumbers(numbers) {
if (numbers.length !== 6) {
MissionUtils.Console.print("[ERROR] 6개의 숫자를 입력해주세요.\n");
} else if (numbers.some((number) => isNaN(number))) {
MissionUtils.Console.print("[ERROR] 숫자만 입력해주세요.\n");
} else if (numbers.some((number) => number < 1 || number > 45)) {
MissionUtils.Console.print("[ERROR] 1~45 사이의 숫자를 입력해주세요.\n");
} else if (new Set(numbers).size !== 6) {
MissionUtils.Console.print("[ERROR] 중복된 숫자를 입력할 수 없습니다.\n");
} else return true;
}

async inputBonusNumber() {
let bonusNumber;
let isValidated = false;
while (!isValidated) {
const inputString = await MissionUtils.Console.readLineAsync(
"\n보너스 번호를 입력해주세요.\n"
);
bonusNumber = Number(inputString.trim());
isValidated = this.validateBonusNumber(bonusNumber);
}
this.bonusNumber = bonusNumber;
}

validateBonusNumber(bonusNumber) {
if (isNaN(bonusNumber)) {
MissionUtils.Console.print("[ERROR] 숫자만 입력해주세요.\n");
} else if (bonusNumber < 1 || bonusNumber > 45) {
MissionUtils.Console.print("[ERROR] 1~45 사이의 숫자를 입력해주세요.\n");
} else if (this.winningNumbers.includes(bonusNumber)) {
MissionUtils.Console.print("[ERROR] 당첨 번호와 중복될 수 없습니다.\n");
} else return true;
}

countMatchingNumbers(lotto) {
let count = 0;
lotto.getNumbers().forEach((number) => {
if (this.winningNumbers.includes(number)) {
count++;
}
});
return count;
}

computeRank(lotto) {
const count = this.countMatchingNumbers(lotto);
const isBonusMatched = lotto.hasNumber(this.bonusNumber);
switch (count) {
case 6:
return "1등";
case 5:
return isBonusMatched ? "2등" : "3등";
case 4:
return "4등";
case 3:
return "5등";
default:
return "꽝";
}
}

computeTotalResult(lottos) {
const result = { "1등": 0, "2등": 0, "3등": 0, "4등": 0, "5등": 0, 꽝: 0 };
for (const lotto of lottos) {
const rank = this.computeRank(lotto);
result[rank]++;
}
return result;
}

printResult(result) {
MissionUtils.Console.print(`
당첨 통계\n---
3개 일치 (5,000원) - ${result["5등"]}개
4개 일치 (50,000원) - ${result["4등"]}개
5개 일치 (1,500,000원) - ${result["3등"]}개
5개 일치, 보너스 볼 일치 (30,000,000원) - ${result["2등"]}개
6개 일치 (2,000,000,000원) - ${result["1등"]}개`);
}

computeProfit(result, money) {
const totalPrize =
result["5등"] * 5000 +
result["4등"] * 50000 +
result["3등"] * 1500000 +
result["2등"] * 30000000 +
result["1등"] * 2000000000;
const profit = ((totalPrize / money) * 100).toFixed(1);
return profit;
}

printProfit(profit) {
MissionUtils.Console.print(`총 수익률은 ${profit}%입니다.`);
}

async play() {
const money = await this.inputMoney();
const lottos = this.publishLotto(money);
this.printLottos(lottos);
await this.inputNumbers();
await this.inputBonusNumber();
const result = this.computeTotalResult(lottos);
this.printResult(result);
const profit = this.computeProfit(result, money);
this.printProfit(profit);
}
}

export default App;
17 changes: 17 additions & 0 deletions src/Lotto.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,25 @@ class Lotto {
if (numbers.length !== 6) {
throw new Error("[ERROR] 로또 번호는 6개여야 합니다.");
}
if (new Set(numbers).size !== 6) {
throw new Error("[ERROR] 중복된 숫자가 있습니다.");
}
if (numbers.some(num => num < 1 || num > 45)) {
throw new Error("[ERROR] 번호는 1과 45 사이여야 합니다.");
}
}

getNumbers() {
return this.#numbers;
}

hasNumber(number) {
return this.#numbers.includes(number);
}

toString() {
return `[${this.#numbers.join(", ")}]`;
}
// TODO: 추가 기능 구현
}

Expand Down