파이썬 응용문제인 야구게임 프로그래밍을 실습해본다. 야구 게임이라고 해서 진짜 야구같은 방식으로 만드는 것이 아니라, 우리가 어릴적에 하던 숫자맞추기 야구게임을 뜻한다.
TeamLab의 최성철 교수님 파이썬 강좌에 올라온 예제이니 동영상을 보고 직접 학습해보면 좋겠다.
야구게임 규칙
- 컴퓨터가 3자리의 Random Number를 생성
- 사용자가 숫자를 입력하여 맞춤
- 사용자의 입력 숫자가 컴퓨터의 Random Number와 일치하는지 확인
- 숫자와 자릿수가 같으면 1 Strike
- 숫자는 있는데 자릿수가 다르면 1 Ball
- 3개의 숫자와 자릿수가 모두 맞으면 3 Strike 으로 성공
예를 들면 이런 식으로 판정한다.
컴퓨터 생성 숫자 | 사용자 입력 숫자 | 판정 |
472 | 123 | 0S 1B |
472 | 547 | 0S 2B |
472 | 247 | 0S 3B |
472 | 742 | 1S 2B |
472 | 472 | 3S |
- 3자리의 자연수를 입력하지 않을 경우 오류표시
- 세 자리 숫자 중 중복된 것이 있을 경우 오류표시
규칙을 보니 어릴적 하던 야구게임의 기억이 떠오른다. 나중에는 좀 더 복잡한 4자리로도 하고 그랬었는데. 프로그래밍은 파이썬 프로그램이 내가 제시한 숫자를 맞추는 것보다는 쉬울 것 같다. 컴퓨터가 내가 생각한 숫자를 최단 경로로 맞추는 것도 재밌을 거 같은데.
최성철 교수의 TEAMLAB 강의에 파이썬 프로그래밍 파일이 올라와 있고 그 파일내의 각 영역에 조건에 맞는 프로그래밍을 해서 숙제를 제출하는 방식이다. 최성철 교수의 파이썬 강좌 평가가 좋은 이유가 이렇게 직접 코드를 짜서 과제를 제출하는 방식으로 되어있기 때문.
파이썬 학습도 결국 본인이 직접 해보고 낑낑거리며 수정해봐야 경험과 실력이 되는 것이지 강의만 보고 아 이런 함수가 있구나 듣고만 넘어간다고 프로그래밍 할 수 있게 되는건 아니니깐.
각 함수단락 만들기
def get_random_number():
# Helper Function - 지우지 말 것
# 100부터 999까지 수를 램덤하게 반환함
return random.randint(100, 999)
맨 먼저 컴퓨터가 세자리 랜덤 숫자를 반환하는 것으로 시작한다. helper function 이라고 되어있어서 어차피 현재 실력에서는 모르는 부분을 정답을 준 것이니 이 부분은 그냥 냅두고 넘어가면 된다.
def is_digit(user_input_number):
# '''
# Input:
# - user_input_number : 문자열 값
# Output:
# - user_input_number가 정수로 변환 가능할 경우는 True,
# 그렇지 않을 경우는 False
# Examples:
# >>> import baseball_game as bg
# >>> bg.is_digit("551")
# True
# >>> bg.is_digit("103943")
# True
# >>> bg.is_digit("472")
# True
# >>> bg.is_digit("1032.203")
# False
# >>> bg.is_digit("abc")
# False
return user_input_number.isdigit()
코드가 이런 식으로 되어 있는데, py 파일을 열어보면 모든 부분이 함수로 def 정의되어 있다. 지금까지 파이썬 명령어 맛배기를 공부했다면 실제로 프로그래밍을 하면 코드파일을 어떻게 구성하는지 훑어볼 수 있는 기회이다.
앞에 #이 붙은것은 전부 설명과 맞게 짰는지 확인하는 방법이 친절하게 기재된 부분이다. 여기서는 is_digit 함수를 정의하는데 이 함수는 문자열 입력값을 받아서 그것이 정수로 변환 가능할 경우는 True, 그렇지 않을 경우는 False를 반환하도록 한다.
Input, Output에 있는 설명이 어떻게 동작하는 프로그래밍을 짜라 라는 숙제 미션으로 이해하면 되고, 밑에 Examples 부분은 코드가 맞게 되었는지 파이썬 콘솔창에서 확인해보라는 예시이다.
VS Code에서 편집한 뒤 저장을 하고, 콘솔창에서 py 파일이 저장된 폴더로 이동한 뒤 파이썬을 실행시킨다. 그리고 examples 에 나와있는 명령어들을 똑같이 복사해서 넣어본 뒤 결과값이 같게 나오는지 확인한다. 이렇게 동일하게 나오면 코드가 제대로 되었다는 뜻이고 # 붙은 주석들을 지워주어도 된다.
def is_between_100_and_999(user_input_number):
# '''
# Input:
# - user_input_number : 문자열 값
# 입력된 값은 숫자형태의 문자열 값임이 보장된다.
# Output:
# - user_input_number가 정수로 변환하여 100이상 1000미만일 경우 True,
# 그렇지 않을 경우는 False
# Examples:
# >>> import baseball_game as bg
# >>> bg.is_between_100_and_999("551")
# True
# >>> bg.is_between_100_and_999("103943")
# False
# >>> bg.is_between_100_and_999("472")
# True
# >>> bg.is_between_100_and_999("0")
# False
# '''
# ===Modify codes below=============
# 조건에 따라 변환되어야 할 결과를 result 변수에 할당
if 100 <= int(user_input_number) <= 999 :
return True
else:
return False
이런 식으로 한 블럭씩 (한 함수씩) 계속 코드를 만들어보고 실행해보는 과정을 반복하면서 과제를 수행하면 된다. 100에서 999 사이의 숫자인지 검증하는 함수로, 입력값을 정수형태로 바꾸어서 100이상 1000미만인 경우 True, 그렇지 않은 경우 False를 반환하는 함수를 작성한다.
마찬가지로 콘솔창에 함수를 호출해서 예제구문들로 검증해보고 맞게 동작하는지 확인한다. 긴 프로그래밍을 다 만들고 한번에 동작시키면 어디가 잘못되었는지 찾아내기 힘드니까 이렇게 한블럭씩 원하는 기능이 잘 돌아가는지 확인하면서 넘어가야 한다.
def is_duplicated_number(three_digit):
# '''
# Input:
# - three_digit : 문자열로 된 세자리 양의 정수 값
# 문자열로 된 세자리 양의 정수값의 입력이 보장된다.
# Output:
# - three_digit 정수로 변환하였을 경우 중복되는 수가 있으면 True,
# 그렇지 않을 경우는 False
# ex) 117 - True, 123 - False, 103 - False, 113 - True
# Examples:
# >>> import baseball_game as bg
# >>> bg.is_duplicated_number("551")
# True
# >>> bg.is_duplicated_number("402")
# False
# >>> bg.is_duplicated_number("472")
# False
# >>> bg.is_duplicated_number("100")
# True
# '''
# ===Modify codes below=============
# 조건에 따라 변환되어야 할 결과를 result 변수에 할당
# Hint - Len과 Set을 써서 할 수 있음, 중복되는 값의 str 길이는 1 또는 2
if len(set(three_digit)) <3:
return True
else:
return False
다음은 입력값에서 중복된 숫자가 있는지 확인하는 함수이다. 힌트에 Len 하고 Set을 써서 할 수 있다고 되어있길래 위와 같이 입력값을 set 자료구조형으로 바꾼 뒤 그것의 길이 len이 3보다 적으면 (즉 중복된 숫자가 있었으면) True 그렇지 않으면 False를 반환하도록 하였다.
마찬가지로 콘솔창에 직접 입력을 해보고 중복된 숫자가 있을시 True가 뜨는지 확인한다.
for number in three_digit:
if three_digit.count(number) > 1:
return True
return False
또는 이렇게 for 문을 이용해서 입력값의 숫자 하나씩을 불러온 뒤, 그 숫자가 입력값에서 몇개 있는지 체크해서 1개 초과이면 True (중복이 있으면) 그렇지 않고 전부 1개씩이라 리턴값 없이 for문이 종료되면 False를 반환하도록 만들어줄 수도 있다.
def is_validated_number(user_input_number):
# '''
# Input:
# - user_input_number : 문자열 값
# Output:
# - user_input_number 값이 아래 조건이면 True, 그렇지 않으면 False를 반환
# 1) 숫자형 문자열이며, 2) 100이상 1000미만이며, 3) 중복되는 숫자가 없을 경우
# Examples:
# >>> import baseball_game as bg
# >>> bg.is_validated_number("amvd")
# False
# >>> bg.is_validated_number("402")
# True
# >>> bg.is_validated_number("472")
# True
# >>> bg.is_validated_number("100")
# False
# >>> bg.is_validated_number("1000")
# False
# '''
# ===Modify codes below=============
# 조건에 따라 변환되어야 할 결과를 result 변수에 할당
if is_digit(user_input_number) is not True:
return False
if is_between_100_and_999(user_input_number) is not True:
return False
if is_duplicated_number(user_input_number):
return False
return True
# ==================================
이번에는 3가지 조건을 모두 만족하는지 필터링하는 구문이다. 숫자형 문자열, 100이상 1000미만, 중복숫자 없을경우 세 가지 조건을 모두 만족시 True 하나라도 어긋나면 False를 반환한다. 각각은 위에서 만든 검증 함수가 이미 있으므로 여기서는 그 함수명들을 불러와서 True/False인지만 확인하면 된다.
is_digit()
is_between_100_and_999()
is_duplicated_number()
위에서 작성한 세 가지 함수를 가져온다.
첫번째 함수는 정수로 변환 가능할 경우 True를 반환했으므로 이게 True가 아닌 경우 False를 반환하도록 if 구문을 작성, 두번째 between 함수도 100~999 사이에 있을 시 True를 반환했으므로 아닐 경우 False, 마지막 중복검사는 중복이면 True 였으므로 if문 뒤에 특별한 말 없으면 True일 경우 아랫줄에 return False를 한다는 뜻이다.
세 가지에서 모두 False가 반환되지 않고 넘어가면 마지막에 True를 반환하는 것으로 마무리.
역시 콘솔창에 제대로 동작하는지 검증
def get_not_duplicated_three_digit_number():
# '''
# Input:
# - None : 입력값이 없음
# Output:
# - 중복되는 숫자가 없는 3자리 정수값을 램덤하게 생성하여 반환함
# 정수값으로 문자열이 아님
# Examples:
# >>> import baseball_game as bg
# >>> bg.get_not_duplicated_three_digit_number()
# 125
# >>> bg.get_not_duplicated_three_digit_number()
# 634
# >>> bg.get_not_duplicated_three_digit_number()
# 583
# >>> bg.get_not_duplicated_three_digit_number()
# 381
# '''
# ===Modify codes below=============
# 조건에 따라 변환되어야 할 결과를 result 변수에 할당
# get_random_number() 함수를 사용하여 random number 생성
random_number = str(get_random_number())
while is_duplicated_number(random_number):
random_number = str(get_random_number())
return random_number
# ==================================
이번엔 컴퓨터가 랜덤한 세자리 숫자를 출력하는데 그것이 중복값을 포함하고 있는 경우라면 while 문으로 다시 랜덤넘버를 생성하도록 한다. 중복숫자 검증함수인 is_duplicated_number 값이 False가 될 때에만 while문을 빠져나와서 return 값으로 반환하도록 한다.
실제로 돌려보면 중복된거 없이 계속 랜덤 세자리 숫자를 보여주는걸 알 수 있고, 만약 중복 숫자가 있을때 재동작 하는게 맞는지 확인하려면 while구문 밑에 print로 한번 출력하고 다시 랜덤넘버 생성하도록 해보면 된다.
이렇게 중복 숫자가 나왔는데 print한번 하고 다시 while문을 돌려서 새로운 랜덤넘버를 반환한 것을 알 수 있다.
def get_strikes_or_ball(user_input_number, random_number):
# '''
# Input:
# - user_input_number : 문자열값으로 사용자가 입력하는 세자리 정수
# - random_number : 문자열값으로 컴퓨터가 자동으로 생성된 숫자
# Output:
# - [strikes, ball] : 규칙에 따라 정수형 값인 strikes와 ball이 반환됨
# 변환 규칙은 아래와 같음
# - 사용자가 입력한 숫자와 컴퓨터가 생성한 숫자의
# 한 숫자와 자릿수가 모두 일치하면 1 Strike
# - 자릿수는 다르나 입력한 한 숫자가 존재하면 1 Ball
# - 세자리 숫자를 정확히 입력하면 3 Strike
# Examples:
# >>> import baseball_game as bg
# >>> bg.get_strikes_or_ball("123", "472")
# [0, 1]
# >>> bg.get_strikes_or_ball("547", "472")
# [0, 2]
# >>> bg.get_strikes_or_ball("247", "472")
# [0, 3]
# >>> bg.get_strikes_or_ball("742", "472")
# [1, 2]
# >>> bg.get_strikes_or_ball("472", "472")
# [3, 0]
# '''
# ===Modify codes below=============
# 조건에 따라 변환되어야 할 결과를 result 변수에 할당
strikes=0
balls=0
if random_number == user_input_number:
return [3,0]
for number in user_input_number:
if number in random_number:
if user_input_number.index(number) == random_number.index(number):
strikes += 1
else:
balls += 1
else:
pass
return [strikes,balls]
# ==================================
이제 야구 게임에서 가장 중요한 부분이라 할 수 있는, 스트라이크, 볼 판정이다. 일단 컴퓨터의 랜덤넘버와 유저 입력넘버가 같을 경우는 3스트라이크 이므로 [3,0]을 반환하면 된다.
그렇지 않다면 유저 입력번호에서 한개의 숫자를 가져온 뒤,
for number in user_input_number:
그 숫자가 랜덤넘버에 있을 경우
if number in random_number:
유저 입력번호와 랜덤 번호에서의 자리수가 같다면
if user_input_number.index(number) == random_number.index(number):
스트라이크 1 추가
strikes += 1
그렇지 않다면 (있는데 자리수는 다르다면) 볼 1 추가
else:
balls += 1
def is_yes(one_more_input):
# '''
# Input:
# - one_more_input : 문자열값으로 사용자가 입력하는 문자
# Output:
# - 입력한 값이 대소문자 구분없이 "Y" 또는 "YES"일 경우 True,
# 그렇지 않을 경우 False를 반환함
# Examples:
# >>> import baseball_game as bg
# >>> bg.is_yes("Y")
# True
# >>> bg.is_yes("y")
# True
# >>> bg.is_yes("Yes")
# True
# >>> bg.is_yes("YES")
# True
# >>> bg.is_yes("abc")
# False
# >>> bg.is_yes("213")
# False
# >>> bg.is_yes("4562")
# False
# '''
# ===Modify codes below=============
# 조건에 따라 변환되어야 할 결과를 result 변수에 할당
if one_more_input.upper() == "YES":
return True
if one_more_input.upper() == "Y":
return True
return False
# ==================================
def is_no(one_more_input):
# '''
# Input:
# - one_more_input : 문자열값으로 사용자가 입력하는 문자
# Output:
# - 입력한 값이 대소문자 구분없이 "N" 또는 "NO"일 경우 True,
# 그렇지 않을 경우 False를 반환함
# Examples:
# >>> import baseball_game as bg
# >>> bg.is_no("Y")
# False
# >>> bg.is_no("b")
# False
# >>> bg.is_no("n")
# True
# >>> bg.is_no("NO")
# True
# >>> bg.is_no("nO")
# True
# >>> bg.is_no("1234")
# False
# >>> bg.is_no("yes")
# False
# '''
# ===Modify codes below=============
# 조건에 따라 변환되어야 할 결과를 result 변수에 할당
if one_more_input.upper() == "NO":
return True
if one_more_input.upper() == "N":
return True
return False
# ==================================
입력값이 대소문자 구분없이 Y or YES 일 경우 True를 반환하는 is_yes 함수, N or NO 일 경우 True를 반환하는 is_no 함수도 만들었다. 이렇게 필요한 함수들을 모두 생성한 뒤 이제 메인 함수를 작성한다.
메인함수 짜기
마지막으로 위에서 작성한 여러가지 함수들을 이용해서 메인 함수를 작성한다. 중복 입력값 판정같은 것을 미리 별도의 함수로 지정해놓고, 메인 함수에서는 그 함수명을 불러와서 코딩을 하면 보다 간결하게 만들 수 있다.
또 코드가 길고 복잡해질수록 어디가 잘못되었는지 고치기도 어려워서, 가급적이면 특정한 기능은 함수들로 만들어놓고 전체 동작하는 메인함수를 따로 해주는 것이 여러모로 편리하다. (직접 해보면 느낀다)
def is_zero(user_input):
if user_input == "0" :
return True
else:
return False
제로 함수를 하나 더 만들어주고, user_input이 0이 들어오면 True값을 주는 함수이다. 이걸 이용해서 숫자입력시 0을 치면 게임을 그만하고 종료할 수 있도록 한다.
def main():
print("Play Baseball")
user_input = 999
random_number = str(get_not_duplicated_three_digit_number())
print("Random Number is : ", random_number)
while(not(is_zero(user_input))):
user_input = input("Input guset number (exit:0): ")
if is_zero(user_input):
continue
if is_validated_number(user_input):
strikes, balls = get_strikes_or_ball(user_input, random_number)
print("Strikes :", strikes, ", Balls :", balls)
if strikes == 3:
while True:
one_more = input("You win, one more(Y/N)?")
if is_yes(one_more):
random_number = str(get_not_duplicated_three_digit_number())
print("Random Number is : ", random_number)
break
elif is_no(one_more):
user_input = "0"
break
else:
print("Wrong Input, Input again")
continue
else:
print("wrong Input, Input again")
print("Thank you for using this program")
print("End of the Game")
if __name__ == "__main__":
main()
대망의 메인함수이다. 강의를 보면서 그대로 타이핑해서 만들었는데 제대로 동작하지 않아서 네이버 지식인에 수정해달라고 질문도 올려보고, 다시 또 혼자 낑낑대면서 연구해가며 만들었다. 티스토리 블로그 스킨 수정하듯이 코드 부여잡고 씨름했다.
0 입력시 종료
가장 큰 틀부터 살펴보면, 먼저 ① 랜덤 넘버를 형성하고 화면에 출력해준다. 그리고 프로그램은 ② user_input이 0이 아닌 동안 while 문으로써 반복적으로 수행된다. ③ user_input이 0이 들어오면 if문에서 continue로 빠져나가면서 while문의 처음으로 돌아간다. while문은 user_input이 0이라 수행을 종료하므로 ④ 로 빠져나가서 프린트문을 수행하고 끝난다.
여기서 3번에 continue를 쓰면 아래 수행문을 하지 않고 반복문의 처음으로 돌아가는 것인데, 이렇게 안하고 그냥 break를 써서 while문을 빠져나가도 되지 않을까? 생각했다. break로 바꿔도 역시 마찬가지로 정상 동작함.
Strike Ball 판정
0이 아닌 user_input이 들어왔을 때 is_validated_number 함수로 3가지 조건을 조사한다. 앞서 작성한 3가지 함수를 종합해서 판정하는 함수로, 숫자형 문자열인지, 100~999 사이 숫자인지, 중복숫자가 없는지 이다.
만약 여기에 걸려서 잘못된 입력으로 판정받으면 ③ else 로 넘어가서 wrong input을 출력하고 다시 상위 while 반복문으로 넘어가서 user_input을 다시 받는다.
정상적인 입력값이라면 ② strikes balls 판정해서 화면에 보여준다. 보여주고 다시 상위 while문으로 돌아가서 user_input을 새로 받는 반복문을 수행하게 된다. user_input 받고 ▶ 정상 입력인지 판정 ▶ 정상이면 strike ball 개수 출력하고 ▶ 다시 user_input 받는 반복
정답 맞췄을 때 게임 계속or종료
자 이제 strikes == 3 이 되었을 때, 즉 정답을 입력했을 때 게임을 계속할지 그만할지 물어보는 부분이다. 아이러니하게도 이 부분이 가장 어려웠다. 정답 맞추면 그냥 축하합니다 하고 게임 종료하는 식이었으면 훨씬 간단한 코드가 되었을 텐데 말이다.
구글 검색해서 파이썬 야구게임 코딩한 다른 글들을 찾아봐도 이렇게 여러가지 함수로 복잡하게 한 경우는 별로 없고 단순히 strike ball 판정만 해주는거면 그다지 어렵지 않게 짤 수 있다.
아무튼,,, 만약 (if) strikes ==3 이 된 경우에 일단 repeat을 True로 먹이고 True인 동안 반복하는 while문을 만들어 준다.
실행예제
코드를 수정하면서 실제로 파이썬 프로그램이 제대로 동작하는지 계속 확인해주는 것이 중요하다.
- 숫자 입력시 strikes balls 판정
- 이상한 숫자 입력시 오류판정 재입력
- 0 입력시 프로그램 종료
- 정답 맞췄을 시 계속할지 질문
- y누르면 게임 재진행 n누르면 종료
- 이때도 이상한 입력시 오류판정 재입력
위와 같은 프로그램 수행의 조건들이 정상적으로 동작하는 것을 확인하였다. 휴… 어려워. 티스토리 블로그 스킨수정도 그렇지만 파이썬 같은것은 더욱더 자기가 직접 이리저리 해보면서 아 이게 이래서 이렇구나 하고 이해해야 하는 것 같다.
그런데 이 야구게임은 컴퓨터가 생성한 랜덤 넘버를 보여주고 맞추는거라 말그대로 파이썬 프로그램 제대로 동작하는지만 고려하면서 프로그래밍 한건데, 랜덤 넘버를 화면에 안보여주고 맞추라고 하면 정말로 야구게임 하듯이 할 수 있을거 같다.
예를 들면 이렇게,,,
직접 맞추니까 더 재밌잖아? ㅋㅋㅋㅋㅋ
여기서 반대로 유저가 번호를 생각하고, 컴퓨터가 불러주는 숫자에 따라 strikes ball을 알려주면 그걸 토대로 컴퓨터가 추리해서 정답을 맞추는 파이썬 프로그래밍도 가능할까? 언젠간 그런것도 만들 수 있는 날이 오길…