추석이 다가오는 시기에 간만에 윷놀이가 생각나 만들어 보았습니다. 사실 소수 세는 Python 알고리즘 공유할 때도 만져 봤죠.
윷은 4가락을 써 2가지 경우의 수를 2 ** 4 == 16가지로 불린 다음 배가 나온 갯수에 맞춰 끗수를 결정하는 보드게임 도구입니다. 경우의 수가 하나씩밖에 없는 윷(모든 윷가락이 배로 누운 상태)과 모(어떤 윷가락도 배를 보이지 않은 상태)가 나오면 던질 기회와 말을 굴릴 기회가 1회씩 추가되지요.
전통적인 윷은 원기둥을 밑면과 수직으로 자른 형태를 사용합니다. 따라서 완전하게 등면과 배면이 나올 확률이 1/2로 갈라지지는 않죠. 위키백과의 ‘윷놀이’문서에서는, 배가 나올 확률을 52%와 60%까지 진폭을 2%씩 나눠 도·개·걸·윷·모의 확률을 따로 계산해 둔 것을 확인하실 수 있습니다. (‘윷놀이의 확률’ 문단) 이 알고리즘에서는 0에서 99까지의 숫자를 난수로 뽑기 앞서, 배와 등을 가르는 구간을 40부터 50까지의 난수로 뽑는 방식을 썼습니다.
- 배와 등을 가르는 구간 이상의 수가 나오면 배가 나오고,
- 미만의 수가 나오면 등이 나오는 방식이죠.
그 외에 Python은 C++와 달리, 배열에 들어가는 총 원소 수를 바꾸는 명령을 기본내장하고 있어서 구현하기 더 편하더라고요.
코드를 쓰기 전에 미리 만들어 뒀던 의사 코드는 다음과 같습니다. 실제로 구현할 때와는 다소 차이가 있습니다. 가령 ‘경우의 수에 따라 끗 산정’이라고 쓴 부분은, 실제로 끗을 산정할 때는 경우의 수가 아니라 배의 수가 나올 때마다 1을 더하는 방식으로 구현한 점 등이 다릅니다. 또 마지막 윷가락을 뒷도로 바꿨습니다.
윷던지기 ※의사 코드
이동 횟수←1회
소비 가능 끗 수←1개(배열)
윷 4개의 난수 추출
난수를 기준으로 윷이 나온 면 판정
윷의 면을 보고 경우의 수에 따라 끗 산정
소비 가능 끗의 해당 끗←산정된 끗
마지막 끗이 '윷'이나 '모'인 경우
이동 횟수+1
소비 가능 끗 수+1
위 의사코드를 참고하여 실제로 쓴 코드는 다음과 같습니다.
import random //난수 추출을 위한 모듈
yut_points = [0] //이제까지 나온 끗수를 받는 배열. 윷과 모가 나오면 기회를 늘리기 위해 list형태로 정의
yut_each = [0, 0, 0, 0] //각 윷가락이 나온 면 초기화. 나중에 0은 등면/1은 배면으로 간주하기 위함
yut_credits = 0 //윷을 더 던질 수 있는 기회 초기화 겸 다음 반복문 프로세스 시행횟수 설정
for i in yut_points: //윷 던지기 반복문
input("윷을 던지겠습니다. Enter 키를 눌러 주세요.") //실제 윷을 던지기 전과 후로 가르기 위한 일시정지.
yut_seq = 0 //각 윷가락의 수 초기화
for j in yut_each: //윷가락 1개씩의 끗수 매기기 반복문
random.seed() //난수 초기화(컴퓨터 시간 기반)
yut_benchmark = random.randrange(40, 51) //등과 배를 가르는 기준점 난수 추출
yut_result = random.randrange(0, 100) //0부터 99의 수 중 yut_benchmark와 견줄 수 추출
if yut_result >= yut_benchmark: //등과 배 가르기. 배가 나올 확률은 50%~60%이므로, 기준점보다 커야 합니다.
print((yut_seq + 1), "번째 윷: 배") //각 윷가락의 결과를 출력
yut_each[yut_seq] = 1 //각 윷가락 중 yut_seq좌표별 결과를 1로 바꿉니다. 0은 등/1은 배
yut_points[yut_credits] += 1 //이번에 던진 윷의 끗수를 배가 나올 때마다 1씩 더해 나중에 판정합니다.
else: //견줄 수 난수가 기준점보다 작았던 경우
print((yut_seq + 1), "번째 윷: 등") //각 윷가락의 결과를 출력. 각 윷가락 배열의 처음 값이 0이었으므로 yut_each는 건너뛰기
yut_seq += 1 //다음 윷가락으로 넘어가기
print(yut_each) //윷가락 끗수 매기기가 끝나고 각 윷가락의 상태 최종 재확인
if yut_points[yut_credits] == 0: //모든 면이 등이었던 경우
yut_points[yut_credits] = 5 //모는 5끗이므로 끗수를 5로 지정
print((yut_credits + 1), "번째로 던져 나온 끗은 >모<입니다.")
if yut_points[yut_credits] == 1: //배가 하나만 누웠던 경우
if yut_each[3] == 1: //마지막 윷을 뒷도용으로 설정했으므로 그 배가 마지막 윷인지 확인
yut_points[yut_credits] = -1 //뒷도 끗수는 뒤로 1
print((yut_credits + 1), "번째로 던져 나온 끗은 >뒷도<입니다.")
else: //마지막 이외의 윷이 배로 나온 경우
print((yut_credits + 1), "번째로 던져 나온 끗은 >도<입니다.")
if yut_points[yut_credits] == 2: //배가 둘 누운 경우
print((yut_credits + 1), "번째로 던져 나온 끗은 >개<입니다.")
elif yut_points[yut_credits] == 3: //배가 셋 누운 경우
print((yut_credits + 1), "번째로 던져 나온 끗은 >걸<입니다.")
elif yut_points[yut_credits] == 4: //모든 윷가락이 배였던 경우
print((yut_credits + 1), "번째로 던져 나온 끗은 >윷<입니다.")
if yut_points[yut_credits] >= 4: //나온 윷이 윷과 모였던 경우 던질 기회를 1번 더 주기 위한 조건문
print("윷이나 모가 나왔어요! 한 번 더 던집시다.")
yut_points.append(0) //새 끗수를 매기기 위한 배열 원소 추가. 역시 기본값은 0으로 시작합니다.
yut_credits += 1 //윷이나 모가 나온 만큼 윷 던지기 반복횟수 추가
yut_each = [0, 0, 0, 0] //새 윷을 던지기 위한 윷가락 상태 초기화
/* 다음은 그동안 얻은 끗수를 순서대로 알려 주기 위한 반복문을 시작합니다. */
yut_credits = 0 //yut_points 배열의 원소 수로 이제까지 던졌던 횟수를 받아올 수 있으므로, 이 변수는 0으로 초기화합니다.
print(len(yut_points), "번 윷을 던져서 나온 끗은 다음과 같습니다.") //반복문 시작 전, 이제까지 윷을 던진 횟수 출력
for k in yut_points: //회차별 끗 알려주기 반복문
print((yut_credits + 1), "번째 끗은", yut_points[yut_credits], "입니다.") //좌표별로 받아둔 끗수를 불러옵니다.
yut_credits += 1 //횟수 추가
input("계속하려면 Enter 키를 눌러 주세요.")
/* 실제 윷놀이 게임을 구현하면, 여기까지 쓴 코드 모두를 def 명령문을 써 별도 함수로 만들고,
이제까지 나온 끗수인 yut_points를 return 명령문으로 본문에 출력시킬 수 있습니다.
윈도우 10 기준으로 py 파일은 명령 프롬프트에서 실행하지 않은 경우 마지막 출력이 끝나면 강제종료되므로,
결과를 확인하기 위해 input 명령문으로 프로세스를 일시정지시켜 줍니다.
C++에서는 system("Pause") 명령문으로 가능하지요. */
그리고 실행하시게 되면 다음과 같은 결과가 나옵니다.
NT커널에서 C++로 컴파일한 프로그램을 일반적인 GUI환경 탐색기에서 실행할 경우, 실행시켰던 명령 프롬프트가 강제종료되는 특성 때문에 언제나 system(“pause”)를 썼던 기억이 납니다. 처음부터 명령 프롬프트를 켜서 CUI환경으로 실행시키면 그런 불편이 덜하지만요.