다음주 목(2024-02-29) 까지 진행할 개인 과제 중
2번 문항을 풀이한 코드를 리뷰하고자 한다.
가위 바위 보 게임 구현이다.
if __name__ == "__main__":
# [무, 승, 패]
status = [0, 0, 0]
set_game()
초기에 파이썬 파일을 실행하면 set_game() 함수를 호출한다.
status 는 전적(무, 승, 패)을 저장하는 list 이다.
주석을 추가해서 status 의 각 요소들이 어떤 것들을 가리키는지 설명했다.
def set_game():
while True:
global status
play_game()
command = input("게임을 다시 하시겠습니까? (종료: 'n', 데이터 초기화 후 재시작 'r'): ")
if command == 'n':
print("게임을 종료합니다.")
print(f"전적: {status[1]}승 {status[2]}패 {status[0]}무")
break
elif command == 'r':
print("이전 게임 플레이어의 전적을 초기화 합니다...")
status = [0, 0, 0]
게임은 초기에 while문을 실행하면서 첫 판을 진행하고
이후 이어서 게임을 이어나갈지 아닐지를 유저 input에 의해 결정한다.
status 를 global을 통해 전역변수화 한다.
play_game() 함수가 실행되면서 게임 진행 후 결과(전적)를 status에 갱신하는 방식이다.
if 문에서 종료 커맨드 'n'과
이전 유저 데이터(저장한 전적 데이터)를 초기화하고 게임을 이어나가는 커맨드 'r'을 판별하고
이외의 입력은 게임을 이어나가는 식이다.
종료 커맨드가 들어오면 전적을 출력하고 while 문을 빠져나오며 프로그램이 종료된다.
초기화 후 재시작 커맨드가 들어오면 전역 변수 status 를 [0, 0, 0] 으로 초기화(initialize)한다.
if문 이후로 while 문 안의 코드를 반복한다.
def play_game(stat):
com_idx = random.randrange(3)
user_input = input(INPUT_TEXT).lower()
if user_input in RSP_DATA["Korean"]:
temp_list = RSP_DATA["Korean"]
elif user_input in RSP_DATA["English"]:
temp_list = RSP_DATA["English"]
else:
print("유효하지 않은 입력입니다. 다시 시도해주십시오.")
return
user_idx = temp_list.index(user_input)
print(f"사용자: {temp_list[user_idx]}, 컴퓨터: {temp_list[com_idx]}")
result = (user_idx - com_idx) % 3
if result == 0:
print("무승부입니다.")
elif result == 1:
print("승리했습니다.")
else:
print("패배했습니다.")
stat[result] += 1
play_game() 함수이다.
해당 함수에서 RSP_DATA, INPUT_TEXT 를 사용하기 때문에 이 둘을 먼저 설명한다.
RSP_DATA = {
"Korean": ["가위", "바위", "보"],
"English": ["scissors", "rock", "paper"]
}
INPUT_TEXT = f"[{', '.join(RSP_DATA['Korean'])}] 또는 [{', '.join(RSP_DATA['English'])}] 중 하나를 선택하여 입력하십시오.: "
우선 RSP_DATA 라는 dict 데이터와, INPUT_TEXT 라는 string 데이터를 정의했다.
RSP_DATA (Rock-Scissor-Paper Data)는
각 언어의 이름(Korean, English)을 Key 값으로
가위, 바위, 보 3개 단어로 이루어진 리스트를 Value 로 저장한 dict 데이터다.
play_game() 함수에선 이 RSP_DATA를 적극적으로 사용한다.
INPUT_TEXT는 유저로부터 input 값을 받을 때 출력하는 string 을 따로 관리하기 위해 정의했다.
실제 실행을 해보면 "[가위, 바위, 보] 또는 [scissors, rock, paper] 중 하나를 선택하여 입력하십시오.: " 라고 출력된다.
★ A.join(input_iterable) 은 input_iterable 의 각 요소를 A라는 string을 사이에 두고 묶은 string을 반환한다.
ex 1) ','.join(['1', '2', '3']) 의 return 값은 "1,2,3" 이다.
ex 2) '-'.join(['010', '1234', '5678']) 의 return 값은 "010-1234-5678" 이다.
ex 3) ' 그리고 '.join(['사과', '배', '감', '오렌지']) 의 return 값은 "사과 그리고 배 그리고 감 그리고 오렌지" 이다.
def play_game(stat):
com_idx = random.randrange(3)
user_input = input(INPUT_TEXT).lower()
if user_input in RSP_DATA["Korean"]:
temp_list = RSP_DATA["Korean"]
elif user_input in RSP_DATA["English"]:
temp_list = RSP_DATA["English"]
else:
print("유효하지 않은 입력입니다. 다시 시도해주십시오.")
return
다시 play_game() 함수로 돌아와서 설명을 이어가겠다.
크게 2개의 파트로 나누어 설명하겠다.
매 판마다 com_idx 에 0, 1, 2 중 무작위의 하나의 숫자를 저장한다.
com_idx 는 RSP_DATA 의 가위, 바위, 보 리스트의 index 값이기도 하다.
즉 com_idx 를 0, 1, 2 중 무작위의 하나의 숫자를 저장한다는 것은 가위, 바위, 보 중 무작위로 하나를 정하는 것과 같다.
유저로부터 문자를 .lower() 함수를 통해 lowercase 로 입력받은 후
입력값이 RSP_DATA 의 각 value(가위, 바위, 보 리스트)에 있는지를 확인한다.
① 존재하면 각 언어에 맞는 리스트를 temp_list 에 지정해주고
존재하지 않으면 유효하지 않은 입력임을 알리고 해당 판을 종료한다.
① 을 부연 설명한다.
ex 1)
유저가 '가위'를 입력하면 user_input 에 '가위' 가 저장되고
user_input 은 RSP_DATA["Korean"], 즉 ["가위", "바위", "보"] 의 요소 중 하나이므로 True 이다.
따라서 RSP_DATA["Korean"] 를 temp_list 에 지정하고, 이를 아래 코드에서 사용한다.
ex 2)
유저가 'Rock'을 입력하면 .lower() 함수에 의해 'rock' 으로 user_input에 저장되고
user_input 은 RSP_DATA["English"], 즉 ["scissors", "rock", "paper"] 의 요소 중 하나이므로 True 이다.
따라서 RSP_DATA["English"] 를 temp_list 에 지정하고, 이를 아래 코드에서 사용한다.
user_idx = temp_list.index(user_input)
print(f"사용자: {temp_list[user_idx]}, 컴퓨터: {temp_list[com_idx]}")
result = (user_idx - com_idx) % 3
if result == 0:
print("무승부입니다.")
elif result == 1:
print("승리했습니다.")
else:
print("패배했습니다.")
status[result] += 1
이후 파트이다.
사실 원래 편하게 생각을 해보면
# [일반적인 코드]
print(f"사용자: {user_input}, 컴퓨터: {temp_list[com_idx]}")
if user_input == "가위":
if temp_list[com_idx] == "가위":
print("무승부입니다.")
status[0] += 1
elif temp_list[com_idx] == "바위":
print("패배했습니다.")
status[2] += 1
else:
print("승리했습니다.")
status[1] += 1
elif user_input == "바위":
if temp_list[com_idx] == "가위":
print("승리했습니다.")
status[1] += 1
elif temp_list[com_idx] == "바위":
print("무승부입니다.")
status[0] += 1
else:
print("패배했습니다.")
status[2] += 1
else:
if temp_list[com_idx] == "가위":
print("패배했습니다.")
status[2] += 1
elif temp_list[com_idx] == "바위":
print("승리했습니다.")
status[1] += 1
else:
print("무승부입니다.")
status[0] += 1
이런 식으로 이중 if 문을 사용해서 작성하면 된다.
하지만 이렇게 쓰면 if 문 안의 비김, 승리, 패배 순서도 달라지고 코드도 길어지고
여러모로 개인적으로 마음에 들지 않았다.
그래서 가위 바위 보 게임을 도식화해서 정리해봤다.

그림의 오른쪽은 가위 바위 보 각각의 승/패 관계이다. 빨간 화살표는 누가 누구를 이기는지를 나타낸다 보면 된다.
보 → 가위 는 가위는 보를 이긴다, 가위 → 바위 는 바위는 가위를 이긴다 는 뜻이다.
이런 관계가 서로 순환하는 것이라고 볼 수 있다.
그리고 이런 순환하는 특징을 녹여낼 수 있는지를 고민해봤다.
그 고민의 답이 그림의 왼쪽이다.
["가위", "바위", "보"] 라는 리스트(temp_list)가 있으면
가위의 index 값은 0, 바위의 index 값은 1, 보의 index 값은 2 이다.
그리고 A가 유저가 낸 손모양, B가 컴퓨터가 낸 손모양 이라고 쉽게 생각해보자.
A = 바위, B = 가위 면 유저가 이긴 거다.
A = 보, B = 가위 면 유저가 진 거다.
A = 가위, B = 가위 면 유저와 컴퓨터는 비긴 거다.
그럼 이제 A, B를 각각 index 값이라고 생각해보자. 위의 예시를 index로 치환해보자.
A = 1, B = 0 면 유저가 이긴 거다. (temp_list[1] 은 바위, temp_list[0] 은 가위)
A = 2, B = 0 면 유저가 진 거다. (temp_list[2] 는 보, temp_list[0] 은 가위)
A = 0, B = 0 면 유저와 컴퓨터는 비긴 거다. (temp_list[0] 은 가위, temp_list[0] 은 가위)
여기까지 이해했으면 가위 바위 보의 모든 경우의 수에 대해 index 관계로 생각해보자
우선 유저와 컴퓨터가 비기는 경우를 보면
A = 0(가위), B = 0(가위)
A = 1(바위), B = 1(바위)
A = 2(보), B = 2(보)
이다. 즉 A - B = 0 이라면 무승부라고 봐도 된다.
다음으로 유저가 이기는 경우를 보면
A = 0(가위), B = 2(보)
A = 1(바위), B = 0(가위)
A = 2(보), B = 1(바위)
이다. 즉 A - B가 특정 값으로 모두 같으면 승리로 보고 싶은데 A = 0, B = 2 일 경우가 성립하질 않는다.
그런데 이를 성립하게 만들어줄 수 있다. 나머지를 이용하면 된다.
(A - B) % 3을 취해주면 위 3가지의 경우 모두 1이 된다. 2를 3으로 나눈 나머지가 1이기 때문이다.
다음으로 유저가 지는 경우를 보면
A = 0(가위), B = 1(바위)
A = 1(바위), B = 2(보)
A = 2(보), B = 0(가위)
이다.
마찬가지로 (A - B) % 3을 취해주면 위 3가지의 경우 모두 2가 된다. -1을 3으로 나눈 나머지가 2이기 때문이다.
정리하면
(A - B) % 3을 했을 때
0이면 무승부
1이면 승리
2이면 패배 이다.
user_idx = temp_list.index(user_input)
print(f"사용자: {temp_list[user_idx]}, 컴퓨터: {temp_list[com_idx]}")
result = (user_idx - com_idx) % 3
if result == 0:
print("무승부입니다.")
elif result == 1:
print("승리했습니다.")
else:
print("패배했습니다.")
status[result] += 1
다시 이 코드를 보면 이해할 수 있다.
위의 논리를 그대로 코드로 옮겨적은 것 뿐이다.
근데 그러면 전적(status)은 어떻게 갱신하는 건가 싶다.
맨 밑에 status[result] += 1 이 그것이다.
result가 0이면 무승부 -> status[0]은 무승부한 횟수를 나타내므로 status[0] += 1은 무승부한 횟수 +1
result가 1이면 승리 -> status[1]은 승리한 횟수를 나타내므로 status[1] += 1은 승리한 횟수 +1
result가 2면 패배 -> status[2]는 패배한 횟수를 나타내므로 status[2] += 1은 패배한 횟수 +1
이렇게 해서 가위 바위 보 게임을 진행하고 전적까지 갱신하게 된다.
단 22줄로 이루어진 함수로
import random
RSP_DATA = {
"Korean": ["가위", "바위", "보"],
"English": ["scissors", "rock", "paper"]
}
INPUT_TEXT = f"[{', '.join(RSP_DATA['Korean'])}] 또는 [{', '.join(RSP_DATA['English'])}] 중 하나를 선택하여 입력하십시오.: "
def play_game():
com_idx = random.randrange(3)
user_input = input(INPUT_TEXT).lower()
if user_input in RSP_DATA["Korean"]:
temp_list = RSP_DATA["Korean"]
elif user_input in RSP_DATA["English"]:
temp_list = RSP_DATA["English"]
else:
print("유효하지 않은 입력입니다. 다시 시도해주십시오.")
return
user_idx = temp_list.index(user_input)
print(f"사용자: {temp_list[user_idx]}, 컴퓨터: {temp_list[com_idx]}")
result = (user_idx - com_idx) % 3
if result == 0:
print("무승부입니다.")
elif result == 1:
print("승리했습니다.")
else:
print("패배했습니다.")
status[result] += 1
def set_game():
while True:
global status
play_game()
command = input("게임을 다시 하시겠습니까? (종료: 'n', 데이터 초기화 후 재시작 'r'): ")
if command == 'n':
print("게임을 종료합니다.")
print(f"전적: {status[1]}승 {status[2]}패 {status[0]}무")
break
elif command == 'r':
print("이전 게임 플레이어의 전적을 초기화 합니다...")
status = [0, 0, 0]
if __name__ == "__main__":
# [무, 승, 패]
status = [0, 0, 0]
set_game()
'PYTHON' 카테고리의 다른 글
[TIL] 20240227 11일차 (1) | 2024.02.27 |
---|---|
[TIL] 20240226 10일차 (1) | 2024.02.26 |
[TIL] 20240222 8일차 (0) | 2024.02.22 |
[TIL] 20240221 7일차 (0) | 2024.02.21 |
[TIL] 20240219 5일차 (0) | 2024.02.19 |