Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
e9e195e
feat(Controller) : 금액, 위닝넘버 구현
JangYEhoon00 Oct 30, 2025
20e0eb2
feat(controller) : 보너스 넘버 입력 구현
JangYEhoon00 Oct 30, 2025
9ab5773
refactor(input) : 가독성 향상을 위한 클래스명 변경
JangYEhoon00 Oct 30, 2025
1d7d1c7
fix(input): 메세지 오타 수정
JangYEhoon00 Oct 30, 2025
51234d4
다른 Pc에서 작업을 하기위한 임시커밋
JangYEhoon00 Nov 2, 2025
fa390ad
다른 Pc에서 작업을 하기위한 임시커밋
JangYEhoon00 Nov 2, 2025
0ef66fc
docs(README.md) : 리드미 파일 갱신
JangYEhoon00 Nov 3, 2025
4145f83
fix(fileName) : 클래스 이름에 따른 파일명 변경
JangYEhoon00 Nov 3, 2025
af7b589
refactor<Money> : 검증 기능 분리 및 입력값 숫자로 변경
JangYEhoon00 Nov 3, 2025
8a7c748
refactor(Numbers) : 로또 번호 입력값 숫자전환 코드 제거
JangYEhoon00 Nov 3, 2025
1d21b03
fix<Inputs>: 오타 수정
JangYEhoon00 Nov 3, 2025
7a3539c
feat(Money) : 시행횟수 검증 로직 추가
JangYEhoon00 Nov 3, 2025
cc7c067
refactor(Money): 에러 메시지 간소화
JangYEhoon00 Nov 3, 2025
32d22ad
docs(README.md): 리드미 내용 수정
JangYEhoon00 Nov 3, 2025
43776ba
feat(Money): 최대 구매 가능회수 계산 구현
JangYEhoon00 Nov 3, 2025
4dcfc8e
refactor(Money): 파라미터 입력 제거
JangYEhoon00 Nov 3, 2025
8d9ea52
feat(Inputs): 구매 금액 입력 기능 추가
JangYEhoon00 Nov 3, 2025
eb39e81
refactor(Money) : 게임 구매 금액 입력
JangYEhoon00 Nov 3, 2025
a1aaaf2
feat(prizeRank): 등수 객체 상수화
JangYEhoon00 Nov 3, 2025
be8c662
feat(Lotto): 티켓 생성 구현
JangYEhoon00 Nov 3, 2025
4c44b42
feat(Lotto) : 입력값과 비교 기능 추가
JangYEhoon00 Nov 3, 2025
acc5b76
Merge pull request #1 from JangYEhoon00/lottoNum
JangYEhoon00 Nov 3, 2025
403b87f
refactor(Lotto): 로또 번호 생성 구조 변경
JangYEhoon00 Nov 3, 2025
bcec133
refactor(Input, Lotto, LottoGenerator): mvc패턴에 따른 구조 변경
JangYEhoon00 Nov 3, 2025
77742e6
refactor(LottoGenerator): 함수의 책임 분리
JangYEhoon00 Nov 3, 2025
cdac3c6
feat:(Lotto) : 게터 추가
JangYEhoon00 Nov 3, 2025
6e08a4b
feat(Calculator) : 불린 배열 전달 메서드 구현
JangYEhoon00 Nov 3, 2025
8e8ef4b
feat(Calculator) : 계산 기능 구현
JangYEhoon00 Nov 3, 2025
e726959
docs(README) : 구현기능에 맞느 리드미 업데이트
JangYEhoon00 Nov 3, 2025
02bb6d4
refactor : 테스트 코드 명세에 맞는 기능 수정
JangYEhoon00 Nov 3, 2025
ebf601e
feat(CalculatorTest) : 계산기능 테스트 코드 작성
JangYEhoon00 Nov 3, 2025
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
115 changes: 115 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,116 @@
# javascript-lotto-precourse

# 프리코스-로또

## 이번 미션에서는 도전하고 싶은 부분

1. MVC 패턴 숙지
2. Airbnb js 코드 컨벤션 적용
3. 메서드의 책임 최소화
4. 테스트 코드 원칙에 의거한 테스트 코드 구현

---

### **1. 모델 (Model): 데이터 관리의 핵심**

- **모델의 역할:**
- **데이터 저장 및 관리:** 애플리케이션에서 사용하는 데이터를 데이터베이스에 저장하고, 필요할 때 데이터를 가져오거나 수정, 삭제하는 역할을 합니다.
- **데이터베이스와 상호작용:** 데이터베이스와 직접 통신하여 데이터를 처리합니다. (예: 테이블 생성, 데이터 검색, 데이터 삽입 등)
- **데이터 유효성 검증:** 데이터가 올바른 형식인지 확인하고, 잘못된 데이터가 저장되지 않도록 합니다. (예: 이메일 형식 검사, 필수 입력 필드 확인)
- **비즈니스 로직 처리:** 데이터와 관련된 복잡한 규칙이나 계산을 처리합니다. (예: 할인율 계산, 사용자 등급 계산 등)

### **2. 뷰 (View): 사용자 인터페이스 담당**

- **뷰의 역할:**
- **사용자 인터페이스 생성:** 웹 페이지를 구성하는 HTML, CSS, JavaScript 코드를 작성하여 사용자에게 보여지는 화면을 만듭니다.
- **데이터 출력:** 모델에서 가져온 데이터를 사용자에게 보기 좋게 출력합니다.
- **템플릿 사용:** HTML에 Ruby 코드를 추가하여 동적인 웹 페이지를 만듭니다. (ERB, Haml 등의 템플릿 엔진 사용)
- **사용자 입력 처리:** 사용자가 입력한 데이터를 화면에서 처리하고, 컨트롤러에 전달합니다.
- **뷰의 예시 (Rails):**
- `app/views` 폴더에 있는 파일 (예: `users/index.html.erb`, `posts/show.html.erb`)
- ERB(Embedded Ruby) 문법을 사용하여 HTML 코드 안에 Ruby 코드를 작성할 수 있습니다.
- **뷰의 특징:**
- 웹 브라우저에 보여지는 HTML, CSS, JavaScript 코드를 포함합니다.
- 모델에서 가져온 데이터를 예쁘게 표현합니다.
- 사용자의 입력에 따라 동적으로 웹 페이지를 변경합니다.

**뷰를 쉽게 이해하는 방법:**

- **무대 디자이너:** 뷰는 마치 무대 디자이너와 같습니다. 사용자에게 보여지는 화면을 디자인하고 꾸밉니다.
- **프레젠테이션 전문가:** 뷰는 데이터를 프레젠테이션하는 전문가입니다. 데이터를 보기 좋게 정리하여 보여줍니다.

### **3. 컨트롤러 (Controller): 모델과 뷰 연결 담당**

- **컨트롤러의 역할:**
- **사용자 요청 처리:** 웹 브라우저에서 온 사용자 요청을 분석하고, 어떤 모델을 사용해야 하는지, 어떤 뷰를 보여줘야 하는지 결정합니다.
- **모델 호출:** 모델을 사용하여 데이터를 가져오거나 변경합니다.
- **뷰 호출:** 데이터를 뷰에 전달하고, 뷰를 실행하여 사용자에게 웹 페이지를 보여줍니다.
- **흐름 제어:** 웹 애플리케이션의 흐름을 제어합니다. (예: 로그인 여부 확인, 사용 권한 확인 등)
- **데이터 전달:** 모델에서 가져온 데이터를 뷰에 전달합니다.
- **컨트롤러의 예시 (Rails):**
- `app/controllers` 폴더에 있는 파일 (예: `UsersController.rb`, `PostsController.rb`)
- 액션(action)이라고 불리는 메서드를 사용하여 요청을 처리합니다.
- **컨트롤러의 특징:**
- 모델과 뷰 사이의 중개자 역할을 합니다.
- 사용자의 요청에 따라 적절한 모델과 뷰를 선택합니다.
- 웹 애플리케이션의 논리적인 흐름을 제어합니다.

**컨트롤러를 쉽게 이해하는 방법:**

- **교통 경찰관:** 컨트롤러는 마치 교통 경찰관과 같습니다. 교통 흐름을 제어하고, 필요에 따라 차량(데이터)을 목적지(뷰)로 안내합니다.
- **매니저:** 컨트롤러는 웹 애플리케이션의 매니저와 같습니다. 사용자의 요청을 처리하고, 필요한 작업을 수행합니다.

---

미션에 따른 역할 구분

- 1개의 로또를 발행할 때 중복되지 않는 6개의 숫자를 뽑는다.
중복되지 않는 6개의 숫자는 화면에 보여주는 역할만 담당하므로 뷰에 해당
- 당첨 번호 추첨 시 중복되지 않는 숫자 6개와 보너스 번호 1개를 뽑는다.
화면에 보여주는 역할만 담당하므로 뷰에 해당
- 당첨은 1등부터 5등까지 있다. 당첨 기준과 금액은 아래와 같다.
- 1등: 6개 번호 일치 / 2,000,000,000원
- 2등: 5개 번호 + 보너스 번호 일치 / 30,000,000원
- 3등: 5개 번호 일치 / 1,500,000원
- 4등: 4개 번호 일치 / 50,000원
- 5등: 3개 번호 일치 / 5,000원
등수에 대한 데이터는 상수화 번호를 확인한 다음 일치하는 번호에 따른 값을 출력
- 로또 구입 금액을 입력하면 구입 금액에 해당하는 만큼 로또를 발행해야 한다.
입력된 값에 따라 데이터를 뷰로 보내기 때문에 있으므로 컨트롤러에 해당
- 로또 1장의 가격은 1,000원이다.
이건 상수로 묶어서 처리
- 당첨 번호와 보너스 번호를 입력받는다.
입력된 값에 따라 데이터를 뷰로 보내기 때문에 있으므로 컨트롤러에 해당
- 사용자가 구매한 로또 번호와 당첨 번호를 비교하여 당첨 내역 및 수익률을 출력하고 로또 게임을 종료한다.
- 사용자가 잘못된 값을 입력할 경우 "[ERROR]"로 시작하는 메시지와 함께 `Error`를 발생시키고 해당 메시지를 출력한 다음 해당 지점부터 다시 입력을 받는다.

---

- 입력값 숫자로 변경
- 시행횟수가 0보다 작을경우 또는 0일경우 예외처리
- 입력금액보다 게임 하는 금액이 클 경우 예외처리
- 에러 메시지 상수화

### Money 클래스에서 해야할일

- 입력받은 금액을 문자열에서 숫자로 변경
- 일어날 수 있는 문제에 대한 예외처리
- 최대 가능 횟수 계산
- 거스름돈 계산

### Lotto 클래스 해야할일

- 생성된 번호 정렬 -> 컨트롤러에서 제공
- 매직넘버와 생성된 번호를 비교후 불리언 값으로 비교 -> 계산기에서 비교
- 입력받은 배열을 숫자 배열로 변경 ->

### LottoGenerator 클래스의 역할

- 랜덤 번호 생성
- 티켓 구매 수 만큼 랜덤 번호 시행 회수

### Calculator 클래스의 역할

- 배열수를 확인
- 배열에 맞게 맞는 수가 있다면 카운트 증가
- 카운트를 배경으로 등수를 매김
39 changes: 39 additions & 0 deletions __tests__/CalculatorTest.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
import Calculator from "../src/model/Calculator.js";

const makeTicket = (numbers) => ({
getNumbers: () => numbers,
});

describe("Calculator - tallyResults", () => {
test("6개 일치 -> rank1 1개", () => {
const tickets = [makeTicket([1, 2, 3, 4, 5, 6])];
const tally = Calculator.tallyResults(tickets, [1, 2, 3, 4, 5, 6], 7);
expect(tally).toEqual({ rank5: 0, rank4: 0, rank3: 0, rank2: 0, rank1: 1 });
});

test("5개+보너스 -> rank2 1개", () => {
const tickets = [makeTicket([1, 2, 3, 4, 5, 7])];
const tally = Calculator.tallyResults(tickets, [1, 2, 3, 4, 5, 6], 7);
expect(tally).toEqual({ rank5: 0, rank4: 0, rank3: 0, rank2: 1, rank1: 0 });
});

test("5개 -> rank3 1개", () => {
const tickets = [makeTicket([1, 2, 3, 4, 5, 8])];
const tally = Calculator.tallyResults(tickets, [1, 2, 3, 4, 5, 6], 7);
expect(tally).toEqual({ rank5: 0, rank4: 0, rank3: 1, rank2: 0, rank1: 0 });
});

test("4개 -> rank4 1개", () => {
const tickets = [makeTicket([1, 2, 3, 4, 9, 10])];
const tally = Calculator.tallyResults(tickets, [1, 2, 3, 4, 5, 6], 7);
expect(tally).toEqual({ rank5: 0, rank4: 1, rank3: 0, rank2: 0, rank1: 0 });
});

test("3개 -> rank5 1개", () => {
const tickets = [makeTicket([1, 2, 3, 9, 10, 11])];
const tally = Calculator.tallyResults(tickets, [1, 2, 3, 4, 5, 6], 7);
expect(tally).toEqual({ rank5: 1, rank4: 0, rank3: 0, rank2: 0, rank1: 0 });
});
});


64 changes: 63 additions & 1 deletion src/App.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,67 @@
import { Console } from "@woowacourse/mission-utils";
import Inputs from "./controller/Inputs.js";
import Money from "./model/money.js";
import LottoGenerator from "./model/LottoGenerator.js";
import Calculator from "./model/Calculator.js";
import PRIZE_RANK from "./constants/prizeRank.js";

class App {
async run() {}
async run() {
const money = await this.#readValidMoney();
const count = money.getPurchaseCount();
Console.print(`${count}개를 구매했습니다.`);

const tickets = LottoGenerator.generateTickets(count);
tickets.forEach((ticket) => {
const formatted = `[${ticket.getNumbers().join(", ")}]`;
Console.print(formatted);
});

const winningNumbers = await Inputs.getWinningNumber();
const bonusNumber = await Inputs.getBonusNumber();

const tally = Calculator.tallyResults(
tickets,
winningNumbers,
bonusNumber
);

this.#printStatistics(tally);
const totalPrize = Calculator.totalPrize(tally);
const rate = Calculator.rate(totalPrize, money.getAmount());
Console.print(`총 수익률은 ${rate}%입니다.`);
}

async #readValidMoney() {
while (true) {
try {
const input = await Inputs.getMoney();
return new Money(input);
} catch (e) {
Console.print(e?.message);
}
}
}

#printStatistics(tally) {
const format = (key, desc) => {
const amount = desc.split(" / ")[1];
const labelMap = {
NO_5: "3개 일치",
NO_4: "4개 일치",
NO_3: "5개 일치",
NO_2: "5개 일치, 보너스 볼 일치",
NO_1: "6개 일치",
};
return `${labelMap[key]} (${amount})`;
};

Console.print(`${format("NO_5", PRIZE_RANK.NO_5)} - ${tally.rank5}개`);
Console.print(`${format("NO_4", PRIZE_RANK.NO_4)} - ${tally.rank4}개`);
Console.print(`${format("NO_3", PRIZE_RANK.NO_3)} - ${tally.rank3}개`);
Console.print(`${format("NO_2", PRIZE_RANK.NO_2)} - ${tally.rank2}개`);
Console.print(`${format("NO_1", PRIZE_RANK.NO_1)} - ${tally.rank1}개`);
}
}

export default App;
22 changes: 19 additions & 3 deletions src/Lotto.js
Original file line number Diff line number Diff line change
@@ -1,18 +1,34 @@
import GAME_RULES from "./constants/game.js";

class Lotto {
#numbers;

constructor(numbers) {
this.#validate(numbers);
this.#numbers = numbers;
this.#numbers = this.#sortNumbers(numbers);
}

#validate(numbers) {
if (numbers.length !== 6) {
if (numbers.length !== GAME_RULES.LOTTO_NUMBER_COUNT) {
throw new Error("[ERROR] 로또 번호는 6개여야 합니다.");
}
const uniqueCount = new Set(numbers).size;
if (uniqueCount !== numbers.length) {
throw new Error("[ERROR] 중복된 번호가 있습니다.");
}
}

#sortNumbers(numbers) {
const SORTED_TICKET = [...numbers].sort((a, b) => a - b);

return SORTED_TICKET;
}

// TODO: 추가 기능 구현
getNumbers() {
return [...this.#numbers];
}
}

export default Lotto;


10 changes: 10 additions & 0 deletions src/constants/errorMessages.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
const ERROR_MESSAGES = {
NOT_NUMBER: "[ERROR] 금액은 숫자여야 합니다.",
BELOW_MINIMUM: "[ERROR] 최소 금액은 1,000원 이상이어야 합니다.",
INVALID_UNIT: "[ERROR] 금액은 1,000원 단위로 입력해야 합니다.",
INVALID_COUNT: "[ERROR] 구매 개수는 1개 이상이어야 합니다.",
EXCEED_LIMIT: "[ERROR] 구매 개수가 금액을 초과할 수 없습니다.",
LOTTO_NUMBER_COUNT: "[ERROR] 로또 번호는 6개여야 합니다.",
};

export default ERROR_MESSAGES;
7 changes: 7 additions & 0 deletions src/constants/game.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
const GAME_RULES = {
LOTTO_NUMBER_COUNT: 6,
MIN_NUMBER: 1,
MAX_NUMBER: 45,
};

export default GAME_RULES;
9 changes: 9 additions & 0 deletions src/constants/prizeRank.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
const PRIZE_RANK = {
NO_1: "6개 번호 일치 / 2,000,000,000원",
NO_2: "5개 번호 + 보너스 번호 일치 / 30,000,000원",
NO_3: "5개 번호 일치 / 1,500,000원",
NO_4: "4개 번호 일치 / 50,000원",
NO_5: "3개 번호 일치 / 5,000원",
};

export default PRIZE_RANK;
48 changes: 48 additions & 0 deletions src/controller/Inputs.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
import { Console } from "@woowacourse/mission-utils";

export default class Inputs {
static async getWinningNumber() {
const INPUT = await Console.readLineAsync("당첨 번호를 입력해 주세요. : ");
const TRIMED_INPUT = INPUT.replaceAll(" ", "");
const WINNING_NUMBER = TRIMED_INPUT.split(",");

return this.#changeToNumber(WINNING_NUMBER).sort((a, b) => a - b);
}

static async getBonusNumber() {
const BONUS_NUMBER = await Console.readLineAsync(
"보너스 번호를 입력해 주세요. : "
);

return Number(BONUS_NUMBER);
}

static async getMoney() {
const MONEY_INPUT = await Console.readLineAsync("금액을 입력해 주세요. : ");

return MONEY_INPUT;
}

static async getGameCount() {
const COUNT_INPUT = await Console.readLineAsync(
"시행횟수를 입력해 주세요. : "
);

return COUNT_INPUT;
}

static async getPurchaseMoney() {
const PURCHASE_MONEY = await Console.readLineAsync(
"구매 금액을 입력해 주세요. : "
);

return PURCHASE_MONEY;
}

static #changeToNumber(numbers) {
const CHECK_NUM = numbers.map((index) => {
return Number(index);
});
return CHECK_NUM;
}
}
Loading