diff --git a/docs/README.md b/docs/README.md index e69de29..de23c0f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,19 @@ +#### 컴퓨터 +* [x] 로또 구입 금액만큼 로또 발행 +* [x] 발행한 로또 오름차순으로 정렬하여 출력 +* [x] 로또 구입 금액 유효성 검사 +* [x] 로또 번호와 당첨 번호 비교 +* [x] 수익률 계산 (둘째자리 반올림) +* [x] 당첨 내역 및 수익률 출력 +* [x] 예외 상황 시 에러 문구 출력 후 다시 입력 받음 + +#### 사용자 +* [x] 당첨 번호 입력 +* [x] 보너스 번호 입력 +* [x] 사용자 입력 유효성 검사 + +#### 조건 +* [x] 함수나 메서드 길이 15라인 초과 금지 +* [x] else 예약어 사용 금지 +* [x] Enum 적용 +* [x] 로직 분리 \ No newline at end of file diff --git a/src/lotto/__init__.py b/src/lotto/__init__.py index 769b83e..aebb9e1 100644 --- a/src/lotto/__init__.py +++ b/src/lotto/__init__.py @@ -1,7 +1,5 @@ -# src/lotto/__init__.py - # 📌 이 패키지는 로또 관련 기능을 제공하는 모듈입니다. -# 외부에서 `from lotto import Lotto`와 같은 방식으로 사용할 수 있도록 +# 외부에서 `from lotto import Lotto`와 같은 방식으로 사용할 수 있도록 # 필요한 모듈을 여기에 등록하세요. # # ✅ 새로운 모듈을 추가할 경우: @@ -10,9 +8,10 @@ # - `flake8`의 F401 경고(`imported but unused`)가 발생하는 경우, `__all__`을 활용해 해결하세요. from .lotto import Lotto # 🎲 로또 번호 생성 및 검증을 위한 클래스 +from .lotto import Rank # 패키지 외부에서 `from lotto import *` 사용 시 제공할 모듈을 명시적으로 정의합니다. -__all__ = ["Lotto"] +__all__ = ["Lotto", "Rank"] # 💡 예시: 새로운 모듈을 추가할 때 # from .other_module import OtherClass # 🆕 예: 새로운 클래스 추가 시 diff --git a/src/lotto/lotto.py b/src/lotto/lotto.py index 9c8c935..499f6ee 100644 --- a/src/lotto/lotto.py +++ b/src/lotto/lotto.py @@ -1,12 +1,77 @@ -from typing import List +from enum import Enum +import random + class Lotto: - def __init__(self, numbers: List[int]): + """ + 로또 번호 생성 및 유효성 검증하는 클래스 + """ + + def __init__(self, numbers: list[int]): + """ + 로또 객체 초기화 + """ self._validate(numbers) - self._numbers = numbers + self._numbers = sorted(numbers) - def _validate(self, numbers: List[int]): + def _validate(self, numbers: list[int]): + """ + 로또 번호 유효성 검사 + (로또 번호 6개, 중복 불가, 1~45의 범위) + """ if len(numbers) != 6: - raise ValueError + raise ValueError("로또 번호는 6개여야 합니다.") + if len(set(numbers)) < 6: + raise ValueError("로또 번호는 중복되어서는 안됩니다.") + if max(numbers) > 45 or min(numbers) < 1: + raise ValueError("로또 번호의 숫자 범위는 1~45까지입니다.") + + @classmethod + def generate_num(cls): + """ + 1~45 사이의 랜덤한 6개의 숫자로 로또 객체 생성 + """ + return cls(random.sample(range(1, 46), 6)) + + def get_numbers(self): + """ + 로또 번호 리스트 반환 + """ + return self._numbers + + def __str__(self): + """ + 로또 번호 문자열로 반환 + """ + return str(self._numbers) + + +class Rank(Enum): + """ + 로또 당첨 순위 정의하는 클래스 + """ + + FIFTH = (3, False, 5_000) + FOURTH = (4, False, 50_000) + THIRD = (5, False, 1_500_000) + SECOND = (5, True, 30_000_000) + FIRST = (6, False, 2_000_000_000) + NONE = (0, False, 0) + + def __init__(self, match_cnt, bonus_match, prize): + """ + Rank 객체 초기화 + """ + self.match_cnt = match_cnt + self.bonus_match = bonus_match + self.prize = prize - # TODO: 추가 기능 구현 + @classmethod + def get_rank(cls, match_cnt, bonus): + """ + 일치 개수와 보너스 번호 여부를 기반으로 당첨 순위 반환 + """ + for rank in cls: + if rank.match_cnt == match_cnt and rank.bonus_match == bonus: + return rank + return cls.NONE diff --git a/src/lotto/main.py b/src/lotto/main.py index 5f270aa..7a1aaa9 100644 --- a/src/lotto/main.py +++ b/src/lotto/main.py @@ -1,6 +1,144 @@ +from lotto import Rank +from lotto import Lotto +""" +로또 프로그램 모듈. +이 모듈은 로또 번호 생성, 당첨 확인, 결과 출력 등의 기능을 포함한다. +""" + + def main(): - # TODO: 프로그램 구현 - pass + """ + 로또 프로그램 메인 함수 + + 1. 사용자에게 금액 입력받고 해당 개수만큼 로또 번호 생성 + 2. 사용자에게서 당첨 번호와 보너스 번호 입력 받음 + 3. 로또 번호와 사용자가 선택한 번호 비교하여 결과 출력 + """ + count = input_price() + lotto_list = [Lotto.generate_num() for _ in range(count)] + print_lotto(lotto_list) + + user_num = user_input() + bonus_num = bonus_input(user_num) + + result, total_prize = compare_lotto(lotto_list, user_num, bonus_num) + print_result(result, total_prize, count) + + +def input_price(): + """ + 사용자에게서 로또 구입 금액 입력받아 유효성 검사하는 함수 + """ + while True: + try: + print("구입금액을 입력해 주세요.") + price = input() + return validate_price(price) + except ValueError as e: + print(f"[ERROR] {e}") + raise + + +def validate_price(price): + """ + 입력된 금액 유효성 검증 후 로또 개수 반환 + """ + if not price.isdigit(): + raise ValueError("[ERROR] 숫자를 입력해 주세요.\n") + if int(price) % 1000 != 0: + raise ValueError("구입 금액은 1,000원으로 나누어 떨어져야 합니다.\n") + if int(price) < 1000: + raise ValueError("구입 금액은 1,000원 이상이어야 합니다.\n") + + return int(price) // 1000 + + +def print_lotto(lotto_list): + """ + 생성된 로또 번호 출력 + """ + print(f"\n{len(lotto_list)}개를 구매했습니다.") + for lotto in lotto_list: + print(lotto) + + +def user_input(): + """ + 사용자로부터 당첨 번호 입력받아 반환 + """ + while True: + print("\n당첨 번호를 입력해 주세요.") + try: + user_num = list(map(int, input().split(","))) + return Lotto(user_num).get_numbers() + except ValueError as e: + print(f"[ERROR] {e}") + + +def bonus_input(user_num): + """ + 사용자로부터 보너스 번호 입력받아 반환 + """ + while True: + try: + bonus_num = input("\n보너스 번호를 입력해 주세요.\n") + return validate_bonus(bonus_num, user_num) + except ValueError as e: + print(f"[ERROR] {e}") + + +def validate_bonus(bonus_num, user_num): + """ + 입력된 보너스 번호 유효성 검사 후 반환 + """ + if not bonus_num.isdigit(): + raise ValueError("숫자를 입력해 주세요.") + if int(bonus_num) in user_num: + raise ValueError("보너스 숫자와 입력한 당첨 번호는 중복되지 않아야 합니다.") + if int(bonus_num) > 45 or int(bonus_num) < 1: + raise ValueError("로또 번호의 숫자 범위는 1~45까지입니다.") + + return int(bonus_num) + + +def compare_lotto(lotto_list, user_num, bonus_num): + """ + 로또 번호와 사용자가 선택한 번호 비교하여 당첨 결과 계산 + """ + result = {rank: 0 for rank in Rank} + total_prize = 0 + + for lotto in lotto_list: + lotto_num = lotto.get_numbers() + match_cnt = len(set(user_num) & set(lotto_num)) + bonus = bonus_num in lotto_num + + rank = Rank.get_rank(match_cnt, bonus) + result[rank] += 1 + total_prize += rank.prize + + return result, total_prize + + +def print_result(result, total_prize, count): + """ + 당첨 결과 출력 + """ + profit_rate = round((total_prize / (count * 1000)) * 100, 2) + + print("\n당첨 통계") + print("---") + for rank in Rank: + if rank == Rank.SECOND: + print(f"{rank.match_cnt}개 일치, 보너스 볼 일치 ({rank.prize:,}원) - " + f"{result[rank]}개") + + if rank != Rank.NONE and rank != Rank.SECOND: + print(f"{rank.match_cnt}개 일치 ({rank.prize:,}원) - " + f"{result[rank]}개") + + print(f"총 수익률은 {profit_rate}%입니다.") + if __name__ == "__main__": main()