diff --git a/docs/README.md b/docs/README.md index e69de29..aeed71f 100644 --- a/docs/README.md +++ b/docs/README.md @@ -0,0 +1,60 @@ +## 기능 요구 사항 +- [X] 입력 기능 + - [X] 반복 입력, 1~9 서로다른 3자리의 수 (ex:123) + - [X] 게임이 끝난 경우 재시작/종료를 선택하는 1과 2 중 하나의 수 + +- [X] 출력 기능 + - [X] 게임 시작 문구 "숫자 야구 게임을 시작합니다." + - [X] 숫자 입력 요구 문구 "숫자를 입력해주세요 : " + - [X] 입력한 수에 대한 결과를 볼, 스트라이크 개수로 표시 + - [X] 볼 먼저 출력 (ex: 2볼 1스트라이크) + - [X] 모두 0개이면 "낫싱" + - [X] 일치 시, 게임 종료 안내 문구 "3개의 숫자를 모두 맞히셨습니다! 게임 종료" + - [X] 게임 새로 시작 여부 확인 질문 "게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요." + +- [X] 컴퓨터 기능 + - [X] 1~9의 서로 다른 임의의 수 3개를 선택 + - [X] 입력한 숫자와 비교하여 ball, strike 카운트 + +- [X] 게임 새로 시작 또는 종료 기능 + - [X] 1 입력 시 재시작 + - [X] 2 입력 시 종료 + +- [X] 예외 처리 및 종료 기능 + - [X] 사용자가 잘못된 값을 입력할 경우 ValueError를 발생 후 게임 종료 + - [X] 3자리 수 입력 시 예외 사항 + - [X] 정자가 아닌 것을 포함한 경우 + - [X] 3자리 수가 아닌 경우 + - [X] 0이 포함된 경우 + - [X] 중복 값이 있는 경우 + - [X] 게임 새로 시작 여부 확인 질문 시 + - [X] 1, 2 이외의 값을 입력한 경우 + +## 기능 추가 +1. make_computer_num() +> 1에서 9까지 서로 다른 임의의 수 3개 생성 기능 함수 추가 [\#1] +2. player_input() +> 플레이어에게 3개의 숫자 입력 받는 기능 함수 추가 [\#2] +3. check_input() +> 플레이어가 입력한 숫자에 대한 결과를 출력하는 함수 추가 [\#3] +4. loop_check() +> 1에서 3까지의 과정을 반복해 3개의 숫자를 모두 맞히면 게임이 종료되는 기능 함수 추가 [\#4] +5. restart_baseball() +> 게임 종료 후 '1' 입력시 다시 시작, '2' 입력시 완전히 종료하는 기능 함수 추가 [\#5] +6. validate_input() +> 잘못된 값을 입력한 경우 ValueError가 발생하여 프로그램을 종료하는 기능 함수 추가 [\#6] +7. same_num_check() +> 입력 값에서 중복되는 수를 인식하는 기능 함수 추가 [\#6] + +## 오류 수정 +1. make_computer_num() 수정 +> 랜덤 숫자 생성 과정에서 숫자 범위가 1에서 8인 오류 1에서 9 정상 작동하도록 수정 [\#7] +>> random.sample(range(1, ~~9~~10), 3) +2. same_num_check() 수정 +> 첫 번째 숫자만 확인하고 바로 반환하는 오류를 전체 배열의 중복을 확인하도록 수정 [\#7] + +## 기능 수정 +1. loop_check() 재귀 호출에서 while 루프로 변경 +> 재귀 호출은 스택 오버플로우를 일으킬 수 있으므로, while 루프로 변경 [\#8] +2. validate_input() ValueError 발생 시 에러 메시지 추가 +> 입력 길이, 입력 숫자, 중복 유무 3가지로로 나눠 에러 메시지 출력 [\#8] \ No newline at end of file diff --git a/src/baseball/main.py b/src/baseball/main.py index cd8d2f3..8fb72d1 100644 --- a/src/baseball/main.py +++ b/src/baseball/main.py @@ -1,9 +1,153 @@ +import random + +def same_num_check(check_array): + """ + 리스트에서 중복된 숫자가 있는지 확인하는 함수. + 중복된 숫자가 있으면 1을 반환하고, 없으면 0을 반환. + """ + return len(set(check_array)) != len(check_array) + + +def is_number (Data): + """ + Data가 숫자인지 확인하는 함수. + 숫자가 아니면 ValueError를 발생시킴. + """ + try: + int(Data) # Data가 int 형식인지 확인 + except ValueError as e: + raise ValueError("숫자만 입력해주세요.") from e # 숫자가 아닌 값 입력시 예외 처리 + + +def validate_input(Data, Criteria): + """ + 사용자가 입력한 값을 검증하는 함수. + - Data: 사용자가 입력한 값 + - Criteria: 예상하는 값의 조건 (정수 또는 리스트 형식) + 다음과 같은 경우 ValueError를 발생시킴: + - 입력 값이 숫자가 아닌 경우 + - 3개의 숫자가 아닌 경우 + - 중복된 숫자가 포함된 경우 + - 0이 포함된 경우 + - 입력 값이 1 또는 2가 아닌 경우 (재시작 시) + """ + # 숫자 확인 + is_number(Data) + + if isinstance(Criteria, int): # Criteria가 int일 경우, player_input()에서 사용 + compare_array = list(map(int, Data)) # Data를 정수형 리스트로 변환 + + # 플레이어가 입력한 값이 예상되는 값의 길이가 아닐 경우 예외 처리 + if (len(compare_array) != Criteria): + raise ValueError("3개의 숫자를 입력해주세요.") + + # 중복된 숫자가 있을 경우 예외 처리 + if (same_num_check(compare_array) == 1): + raise ValueError("숫자 중복 없이 입력해주세요.") + + # 1~9의 숫자가 아닌 0이 포함된 경우 예외 처리 + if (0 in compare_array): + raise ValueError("1 ~ 9의 숫자만 입력해주세요.") + + return compare_array # 입력이 정상이면 정수 리스트 변환 + + elif isinstance(Criteria, list): # Criteria가 list일 경우, restart_baseball()에서 사용 + # Data가 Criteria에 포함되지 않은 값일 경우 예외 처리 + if int(Data) not in Criteria: + raise ValueError("숫자 1 또는 2만 입력해주세요.") + + +def make_computer_num(): + """ + 컴퓨터가 랜덤으로 3개의 숫자를 생성하는 함수. + 1에서 9까지의 숫자 중에서 중복 없이 3개를 뽑음. + """ + computer = random.sample(range(1, 10), 3) # 1~9 사이에서 중복 없이 3개 숫자 선택 + return computer + + +def player_input(): + """ + 플레이어에게 숫자 3개를 입력받는 함수. + 입력 값 검증 후 정상적인 값을 반환. + """ + get_array = validate_input(input("숫자를 입력해주세요 : "), 3) # 입력 값 검증 + return get_array + + +def check_input(get, com): + """ + 플레이어가 입력한 값과 컴퓨터가 생성한 값을 비교하여 결과를 출력하는 함수. + - get: 플레이어가 입력한 숫자 리스트 + - com: 컴퓨터가 생성한 숫자 리스트 + 결과에 따라 '스트라이크', '볼', '낫싱'을 반환. + """ + ball, strike = 0, 0 # 볼과 스트라이크를 카운트할 변수 초기화 + + for i in range(0,3): # 입력된 숫자와 컴퓨터가 생성한 숫자를 비교 + if get[i] == com[i]: # 숫자와 위치가 맞으면 스트라이크 + strike += 1 + elif get[i] in com: # 숫자는 맞지만 위치가 다르면 볼 + ball += 1 + else: + continue # 숫자도 위치도 맞지 않으면 아무것도 하지 않음 + + # 스트라이크가 3이면 3스트라이크 반환 + if strike == 3: + return "3스트라이크" + + # 스트라이크와 볼이 모두 없으면 낫싱 반환 + elif ball + strike == 0: + return "낫싱" + + result = [] # 결과를 저장할 리스트 + + result.extend([f"{ball}볼" for _ in [1] if ball > 0]) # 볼이 하나 이상 있으면 볼 정보 추가 + result.extend([f"{strike}스트라이크" for _ in [1] if strike > 0]) # 스트라이크가 하나 이상 있으면 스트라이크 정보 추가 + + # 볼과 스트라이크가 있을 경우 공백으로 구분해서 반환 + return " ".join(result) + + +def loop_check(com): + """ + 게임을 반복하여 진행하는 함수. + 플레이어가 3개의 숫자를 모두 맞힐 때까지 반복. + """ + while True: + get = player_input() # 플레이어 입력 받기 + result_check_input = check_input(get, com) # 입력한 값과 컴퓨터 값 비교 + print(result_check_input) # 결과 출력 + if result_check_input == "3스트라이크": # 3스트라이크면 게임 종료 + print("3개의 숫자를 모두 맞히셨습니다! 게임 종료") + return 0 # 게임 종료 + + +def restart_baseball(): + """ + 게임 종료 후 '1'을 입력하면 게임을 새로 시작하고, '2'를 입력하면 종료하는 함수. + """ + print("게임을 새로 시작하려면 1, 종료하려면 2를 입력하세요.") + restart_input = input() # 플레이어 입력 받기 + validate_input(restart_input, [1, 2]) # 입력 값 검증 + return int(restart_input) + + def main(): """ 프로그램의 진입점 함수. 여기에서 전체 프로그램 로직을 시작합니다. """ # 프로그램의 메인 로직을 여기에 구현 + print("숫자 야구 게임을 시작합니다.") + while True: + com_num = make_computer_num() # 컴퓨터가 생성한 숫자 + loop_check(com_num) # 게임 진행 + restart = restart_baseball() # 게임 재시작 여부 확인 + if restart == 1: # 1을 입력하면 게임 재시작 + continue + else: # 2를 입력하면 게임 종료 + break if __name__ == "__main__": # 프로그램이 직접 실행될 때만 main() 함수를 호출 diff --git a/tests/test_main.py b/tests/test_main.py index 81a176e..225e276 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -8,19 +8,19 @@ def test_게임종료_후_재시작(capsys): # 랜덤값 임의 정의 with patch('random.sample', side_effect=[[1, 3, 5], [5, 8, 9]]): # 입력값 모의 처리 - with patch('builtins.input', side_effect=["246", "135", "1", "597", "589", "2"]): + with patch('builtins.input', side_effect=["246", "135", "1", "512", "894", "597", "589", "2"]): main() # 출력값 캡처 캡처된_출력 = capsys.readouterr().out # 결과 검증 - assert all(예상_출력 in 캡처된_출력 for 예상_출력 in ["낫싱", "3스트라이크", "1볼 1스트라이크", "3스트라이크", "게임 종료"]) + assert all(예상_출력 in 캡처된_출력 for 예상_출력 in ["낫싱", "3스트라이크", "1볼 1스트라이크", "1스트라이크", "2볼", "3스트라이크", "게임 종료"]) # 예외 테스트 def test_예외_테스트(): with pytest.raises(ValueError): # 잘못된 입력 처리 - with patch('builtins.input', side_effect=["1234"]): + with patch('builtins.input', side_effect=["1234", "012", "233", "3", "ㄱ"]): main()