[파이썬 프로그래밍 21] 간단한 포커 게임 만들기 3: 함수 만들기

Home / 파이썬 프로그래밍 / [파이썬 프로그래밍 21] 간단한 포커 게임 만들기 3: 함수 만들기

간단한 포커 게임 만들기 3
함수 만들기

포커게임은 한번만 하는 게임이 아니라, 여러번 반복하는 게임입니다. 게임마다 카드를 섞어 패를 나눠주고 나눠가진 패가 어떤 패인지 알아아내는 과정을 반복합니다.

그러면 지금까지 만들었던 코드를 연결해서 한번하는 포커게임을 만들어 보겠습니다. 과정은 이렇습니다.

필요한 모듈을 미리 다 불러온다.
카드 한 팩을 만든다.
카드를 섞는다.
선수 A에게 카드 다섯장을, 선수 B에게도 카드 다섯장을 카드 맨 앞장부터 뺴서 준다.
각 선수가 가진 패가 뭔지 알아본다.
결과 출력한다.

지난번 글에서 이미 각각의 과정을 코드로 만들었습니다. 이들을 이어 붙이는 것입니다. 전체 코드는 다음과 같습니다.

# 과정 1: 필요한 모듈을 미리 다 불러온다.
import random

# 과정 2: 카드 한 팩을 만든다.
deck = [(suit, k) for  suit in ["s", "h", "d", "c"] for k in range(1,14)]

# 과정 3: 카드를 섞는다.
random.shuffle(deck)

# 과정 4: 선수 A에게 카드 다섯장을 선수 B에게 카드 다섯장을 섞은 카드 맨 앞부터 뺴서 준다.
cards_A = [ deck[k] for k in range(0, 5)]
cards_B = [ deck[k] for k in range(5, 10)]

# 과정 5: 각 선수가 가진 패가 뭔지 알아본다.
# 먼저 선수 A의 패가 뭔지 알아본다.
paircount = 0
for n1 in range(0, 4):
    for n2 in range(n1+1, 5):
        if cards_A[n1][1] == cards_A[n2][1] :
            paircount = paircount+1
            
num = [cards_A[k][1] for k in range(5)]
num.sort()
straightox = False
if paircount == 0:
    if (num[4]-num[0]) == 4:
        straightox = True
    if num[0] == 1 and num[1] == 10:
        straightox = True
        
suit = [cards_A[k][0] for k in range(5)]
suit.sort()
flushox = False
if suit[0] == suit[4]:
    flushox = True
    
if straightox and flushox:
    rank_A = 1
elif paircount == 6:
    rank_A = 2
elif paircount == 4:
    rank_A = 3
elif flushox:
    rank_A = 4
elif straightox:
    rank_A = 5
elif paircount == 3:
    rank_A = 6
elif paircount == 2:
    rank_A = 7
elif paircount == 1:
    rank_A = 8
else:
    rank_A = 9

# 선수 B의 패가 뭔지 알아본다.
paircount = 0
for n1 in range(0, 4):
    for n2 in range(n1+1, 5):
        if cards_B[n1][1] == cards_B[n2][1] :
            paircount = paircount+1
            
num = [cards_B[k][1] for k in range(5)]
num.sort()
straightox = False
if paircount == 0:
    if (num[4]-num[0]) == 4:
        straightox = True
    if num[0] == 1 and num[1] == 10:
        straightox = True
        
suit = [cards_B[k][0] for k in range(5)]
suit.sort()
flushox = False
if suit[0] == suit[4]:
    flushox = True
    
if straightox and flushox:
    rank_B = 1
elif paircount == 6:
    rank_B = 2
elif paircount == 4:
    rank_B = 3
elif flushox:
    rank_B = 4
elif straightox:
    rank_B = 5
elif paircount == 3:
    rank_B = 6
elif paircount == 2:
    rank_B = 7
elif paircount == 1:
    rank_B = 8
else:
    rank_B = 9
    
# 과정 6: 결과 출력력하기
print(rank_A, cards_A)
print(rank_B, cards_B)
if rank_A < rank_B:
    print("선수 A가 이겼습니다.")
elif rank_B < rank_A:
    print("선수 B가 이겼습니다.")
else:
    print("비겼습니다.")

코드가 지나치게 길어서, 코드가 어떤 구조인지를 한번에 알아보기가 어렵습니다.

특히 다섯번째 과정, 다시 말해 각 선수들이 가진 패 cards_A와 cards_B가 어떤 순위의 패인지를 따져서 순위를 rank_A와 rank_B에 저장하는 일입니다. 그런데 다섯번째 과정의 코드가 유난히 깁니다. 더군다나 사실상 같은 과정을 선수 A의 카드와 선수 B의 카드에 반복했습니다.

이 과정은 간단하게

# 과정 5: 각 선수가 가진 패가 뭔지 알아본다.
rank_A = CardRank(cards_A)
rank_B = CardRank(cards_B)

로 할 수 있으면 코드가 상당히 짧아질 것 같습니다. 여섯번쨰 과정도 짧지는 않으니, 이것도 다음과 같은 코드로 줄이면 좋을 듯 싶습니다.

# 과정 6: 결과 출력력하기
DisplayResult(cards_A, rank_A, cards_B, rank_B)

그러면 전체의 코드가 다음과 같이 짧아집니다.

# 과정 1: 필요한 모듈을 미리 다 불러온다.
import random

# 과정 2: 카드 한 팩을 만든다.
deck = [(suit, k) for  suit in ["s", "h", "d", "c"] for k in range(1,14)]

# 과정 3: 카드를 섞는다.
random.shuffle(deck)

# 과정 4: 선수 A에게 카드 다섯장을 선수 B에게 카드 다섯장을 섞은 카드 맨 앞부터 뺴서 준다.
cards_A = [ deck[k] for k in range(0, 5)]
cards_B = [ deck[k] for k in range(5, 10)]

# 과정 5: 각 선수가 가진 패가 뭔지 알아본다.
rank_A = CardRank(cards_A)
rank_B = CardRank(cards_B)

# 과정 6: 결과 출력력하기
DisplayResult(cards_A, rank_A, cards_B, rank_B)

게임이 어떻게 진행되는지 한번에 파악할 수 있을 만큼 코드가 충분히 짧습니다. 그런데 CardRank라는 함수와 DisplayResult라는 함수를 만들어야 합니다.

지금까지는 이미 만들어진 함수를 쓰기만 했지만, 이제 함수를 직접 만들어야 합니다.

함수를 만들려면 함수에 무엇이 들어가고, 그것으로 어떤 작업을 하며, 무엇을 내보내는지를 정확히 알아야 합니다. CardRank함수의 경우, 함수에 들어가는 것은 선수의 카드 패이고, 이 카드패가 어떤 패인지 알아내는 작업을 하고, 어떤 패인지 알려주는 순위를 내보냅니다.

이제 CardRank함수를 만들어 보겠습니다.

함수를 만드는 코드 첫줄은 def로 시작해서 함수이름을 적고 그다음에느 소괄호안에 들어가는 것을 적고, :를 맨 마지막에 붙이면 됩니다. 다음 줄 부터는 들여쓰기를 하면서 함수가 하는 작업을 씁니다. 그리고 함수에서 나오는 값은 return과 함께 써서 마무리 하면됩니다.

def CardRank(cards):
    ...
    ...
    return rank

이 함수의 코드는 이미 이전의 긴 코드에서 해봤습니다. 그 코드를 복사해서 붙인 다음, cards_A나 cards_B는 cards로, rank_A나 rank_B는 rank로 바꾸면 됩니다. 이면정확한 코드는 아래와 같습니다.

def CardRank(cards):
    paircount = 0
    for n1 in range(0, 4):
        for n2 in range(n1+1, 5):
            if cards[n1][1] == cards[n2][1] :
                paircount = paircount+1
    num = [cards[k][1] for k in range(5)]
    num.sort()
    straightox = False
    if paircount == 0:
        if (num[4]-num[0]) == 4:
            straightox = True
        if num[0] == 1 and num[1] == 10:
            straightox = True
    suit = [cards[k][0] for k in range(5)]
    suit.sort()
    flushox = False
    if suit[0] == suit[4]:
        flushox = True
    if straightox and flushox:
        rank = 1
    elif paircount == 6:
        rank = 2
    elif paircount == 4:
        rank = 3
    elif flushox:
        rank = 4
    elif straightox:
        rank = 5
    elif paircount == 3:
        rank = 6
    elif paircount == 2:
        rank = 7
    elif paircount == 1:
        rank = 8
    else:
        rank = 9
    return rank

여섯번째 과정도 이전 코드에서 복사해서 붙이면 아래와 같이 함수로 만들 수 있습니다.

def DisplayResult(cards_A, rank_A, cards_B, rank_B):
    print(rank_A, cards_A)
    print(rank_B, cards_B)
    if rank_A < rank_B:
        print("선수 A가 이겼습니다.")
    elif rank_B < rank_A:
        print("선수 B가 이겼습니다.")
    else:
        print("비겼습니다.")

DisplayResult는 화면에 결과를 출력하는 작업을 하지만, 굳이 어떤 값을 내보낼 필요가 없기때문에 맨 마지막줄에 return 을 사용하지 않아도 됩니다.

이제 모든 것을 하나로 묶어 보겠습니다.

가장 먼저 할일은 필요한 모듈을 불러오는 겁니다.
그런 다음에 함수를 만드는 코드를 씁니다. 함수가 이미 만들어져 있어야지 함수를 실제로 쓸 수 있기 때문입니다.
그런 다음 메인 포커게임 코드를 씁니다.

전체 코드는 다음과 같습니다.

# 과정 1: 필요한 모듈을 미리 다 불러오고 함수를 만든다.
import random

def CardRank(cards):
    paircount = 0
    for n1 in range(0, 4):
        for n2 in range(n1+1, 5):
            if cards[n1][1] == cards[n2][1] :
                paircount = paircount+1
    num = [cards[k][1] for k in range(5)]
    num.sort()
    straightox = False
    if paircount == 0:
        if (num[4]-num[0]) == 4:
            straightox = True
        if num[0] == 1 and num[1] == 10:
            straightox = True
    suit = [cards[k][0] for k in range(5)]
    suit.sort()
    flushox = False
    if suit[0] == suit[4]:
        flushox = True
    if straightox and flushox:
        rank = 1
    elif paircount == 6:
        rank = 2
    elif paircount == 4:
        rank = 3
    elif flushox:
        rank = 4
    elif straightox:
        rank = 5
    elif paircount == 3:
        rank = 6
    elif paircount == 2:
        rank = 7
    elif paircount == 1:
        rank = 8
    else:
        rank = 9
    return rank
        
def DisplayResult(cards_A, rank_A, cards_B, rank_B):
    print(rank_A, cards_A)
    print(rank_B, cards_B)
    if rank_A < rank_B:
        print("선수 A가 이겼습니다.")
    elif rank_B < rank_A:
        print("선수 B가 이겼습니다.")
    else:
        print("비겼습니다.")
        

# 과정 2: 카드 한 팩을 만든다.
deck = [(suit, k) for  suit in ["s", "h", "d", "c"] for k in range(1,14)]

# 과정 3: 카드를 섞는다.
random.shuffle(deck)

# 과정 4: 선수 A에게 카드 다섯장을 선수 B에게 카드 다섯장을 섞은 카드 맨 앞부터 뺴서 준다.
cards_A = [ deck[k] for k in range(0, 5)]
cards_B = [ deck[k] for k in range(5, 10)]

# 과정 5: 각 선수가 가진 패가 뭔지 알아본다.
rank_A = CardRank(cards_A)
rank_B = CardRank(cards_B)

# 과정 6: 결과 출력력하기
DisplayResult(cards_A, rank_A, cards_B, rank_B)

함수를 만드는 코드, 다시 말해 def로 시작되는 코드를 다 건너뛴 다음에 오는 코드가 사실상 메인 코드입니다.
빈줄은 제외하고, 코멘트인 줄까지 합친 메인 코드의 길이는 12줄, 코멘트까지 빼면 7줄에 불과하기 때문에, 한눈에 프로그램 구조를 파악할 수 있습니다.

지금까지 한 코드는 아직 게임이라고 하기에는 많이 부족합니다. 사람이 게임에 직접 개입하는 부분이 사실상 없기 때문입니다.
다음 글에서는 사람이 개입하는 부분을 보강해보겠습니다.

그전에 마지막으로 여러번 반복해 패를 제대로 알아내는지 확인할 필요가 있겠습니다. 처음 부분에 random.seed(0)을 추가해 누구나 같은 결과가 나오게 한 다음, 메인 코드를 for 블록 안에 넣어 여러번 반복하는 방법으로 잘 돌아가나 확인해보겠습니다. 아래 코드는 편의상 메인 코드만 썼습니다.

random.seed(0)
for n in range(1000):
    # 과정 2: 카드 한 팩을 만든다.
    deck = [(suit, k) for  suit in ["s", "h", "d", "c"] for k in range(1,14)]
    # 과정 3: 카드를 섞는다.
    random.shuffle(deck)
    # 과정 4: 선수 A에게 카드 다섯장을 선수 B에게 카드 다섯장을 섞은 카드 맨 앞부터 뺴서 준다.
    cards_A = [ deck[k] for k in range(0, 5)]
    cards_B = [ deck[k] for k in range(5, 10)]
    # 과정 5: 각 선수가 가진 패가 뭔지 알아본다.
    rank_A = CardRank(cards_A)
    rank_B = CardRank(cards_B)
    # 과정 6: 결과 출력력하기
    DisplayResult(cards_A, rank_A, cards_B, rank_B)

4장의 카드 숫자가 같은 경우인 포카드는 다섯장의 패로 하는 경우는 그리 자주 나오는 편이 아닙니다. 게임을 몇번해야 포카드가 나오는지야 한번 보겠습니다.

일단은 while True: 블록을 써서 게임을 무한히 반복하겠습니다. 그안에서 게임이 반복될때마다 게임을 한 갯수를 나타내는 변수 n의 값을 1씩 늘려 나가겠습니다. 물론 while 블록에 들어가기 전에 변수 n의 값에 0을 저장합니다. 그러다가 포카드의 순위(rank)인 rank_A나 rank_B에 나오면 break로 while블록에서 빠져나온 다음, 변수 n을 프린트하면 되겠습니다. 너무 많은 출력을 피하기 위해 DidplayResult함수는 포카드가 나왔을때만 사용하겠습니다.

random.seed(0)
n = 0
while True:
    n = n+1
    # 과정 2: 카드 한 팩을 만든다.
    deck = [(suit, k) for  suit in ["s", "h", "d", "c"] for k in range(1,14)]
    # 과정 3: 카드를 섞는다.
    random.shuffle(deck)
    # 과정 4: 선수 A에게 카드 다섯장을 선수 B에게 카드 다섯장을 섞은 카드 맨 앞부터 뺴서 준다.
    cards_A = [ deck[k] for k in range(0, 5)]
    cards_B = [ deck[k] for k in range(5, 10)]
    # 과정 5: 각 선수가 가진 패가 뭔지 알아본다.
    rank_A = CardRank(cards_A)
    rank_B = CardRank(cards_B)
    # 과정 6: 결과 출력력하기
    if rank_A == 2 or rank_B == 2:
        print(n)
        DisplayResult(cards_A, rank_A, cards_B, rank_B)
        break

1410번 게임을 하니까 나옵니다. 그리 흔한 패는 아님을 확인할 수 있습니다.

스트레이트 플러쉬 (순위 1)은 얼마만에 나올까요? 훨씬 더 많은 게임을 해야 나올 겁니다. 물론 아주 적은 확률이지만 바로 나올 수도 있습니다. 로또 당첨자가 있는 것 처럼 말입니다.

random.seed(0)
n = 0
while True:
    n = n+1
    # 과정 2: 카드 한 팩을 만든다.
    deck = [(suit, k) for  suit in ["s", "h", "d", "c"] for k in range(1,14)]
    # 과정 3: 카드를 섞는다.
    random.shuffle(deck)
    # 과정 4: 선수 A에게 카드 다섯장을 선수 B에게 카드 다섯장을 섞은 카드 맨 앞부터 뺴서 준다.
    cards_A = [ deck[k] for k in range(0, 5)]
    cards_B = [ deck[k] for k in range(5, 10)]
    # 과정 5: 각 선수가 가진 패가 뭔지 알아본다.
    rank_A = CardRank(cards_A)
    rank_B = CardRank(cards_B)
    # 과정 6: 결과 출력력하기
    if rank_A == 1 or rank_B == 1:
        print(n)
        DisplayResult(cards_A, rank_A, cards_B, rank_B)
        break

무려 12만번 이상 게임을 해서야 스트레이트 플러쉬가 나왔습니다.
스트레이트 플러쉬가 가능한 경우는 (로열 플러쉬까지 포함) 각 무늬당 [1,2,3,4,5]에서 [10,11,12,13,1]까지 10개씩 총 40개입니다.
그런데 52개의 카드에서 5개의 카드를 뽑을 경우의 수는

$$ _{52}\text C_5 = \frac{52!}{5!(52-5)!} = \frac{52\times51\times50\times49\times48}{5\times4\times3\times2
\times1}=2598960$$

입니다.

결국 스트레이트 플러쉬가 나올 확률은

$$\frac{40}{_{52}\text C_5} = \frac{40}{2598960} = \frac{1}{64974} $$

입니다. 혼자 포커를 한다면 평균 64974번 게임을 해야 한번 나오고, 두사람이 포커를 한다면 평균 32487번 게임을 해야 한번 나오는 수준입니다.

로열 플러쉬는 무늬가 같으면서 스트레이트가 10 11(J), 12(Q), 13(K), 1(A)인 경우입니다. 무늬별로 하나씩 총 4개의 경우가 존재합니다. 우리가 만든 코드에서 스트레이트 플러쉬에서 따로 쳐주지 않았습니다. 굳이 로열 플러쉬가 나올 확률은 계산하면

$$ \frac{4}{_{52}\text C_5} = \frac{4}{2598960} = \frac{1}{649740} $$

입니다. 혼자 포커를 한다면 평균 649740번 게임을 해야 한번 나오고, 다섯명이 한다해도 13만번 정도 게임을 해야 한번 나오는 수준입니다.

쉬지않고 잠도 안자고 다섯명이 1분에 1게임씩한다면 3달에 한번 나오는 수준입니다. 그것도 남이 이 패를 가지는 경우까지 포함한 겁니다. 본인이 직접 잡는 로열 플러쉬는 3달의 5배인 15개월에 한번 나오는 수준입니다. 알란탐과 유덕화가 주연한 1980년대 홍콩영화인 ‘지존무상’에서는 불과 몇게임해서 바로 나온 그 로열 플러쉬가 말입니다.