[파이썬 프로그래밍 20] 간단한 포커 게임 만들기 2. 카드를 섞는(shuffling) 코드와 패가 무엇인지 알아내는 코드

Home / 파이썬 프로그래밍 / [파이썬 프로그래밍 20] 간단한 포커 게임 만들기 2. 카드를 섞는(shuffling) 코드와 패가 무엇인지 알아내는 코드

간단한 포커 게임 만들기 2.
카드를 섞는(shuffling) 코드와 패가 무엇인지 알아내는 코드

포커게임의 기본은 선수들이 나눠가진 카드 패(hand)의 순위(rank)를 정하는 겁니다. 패는 포커 게임의 종류에 따라 각선수가 5개의 카드 패를 받기도 하고, 7개의 카드를 받기도 하고, 5개의 카드는 공유하고 각자 2개의 카드를 받아 결국은 각자 7개의 카드를 받는 등 카드를 받는 방식은 몇가지가 있습니다. 그중에서 가장 간단한 방식인 5장의 카드 패를 받는 포커 게임 방식으로 문제를 풀어가겠습니다.

먼저 선수(player)에게 패(hand)를 나눠줘야겠습니다. 실제 포커 게임에서는 게임을 관리하는 딜러(dealer)가 카드 팩을 잘 섞은 후에, 선수에게 패를 나눠줍니다. 카드를 섞기전에 먼저 카드가 순서대로 들어가있는 목록을 만들겠습니다.

deck = [(suit, k) for suit in ["s", "h", "d", "c"] for k in range(1,14)]
print(deck)

카드를 섞는(shuffling) 코드는, random모듈의 shuffle함수를 이용하겠습니다.

import random
random.shuffle(deck)
print(deck)

카드가 순서대로 있지않고 섞여 있습니다. 이제 섞여있는 카드 팩의 첫번째 카드부터 하나 하나 선수에게 나뉘주면 됩니다.

cards = [ deck[k] for k in range(5)]
print(cards)

이제 선수가 들고있는 패가 뭔지를 따져보겠습니다.

1. 똑같은 숫자의 카드가 2장만 있을 경우를 원 페어(one pair)라고 합니다. 예를 들면, 3,3,5,7,9 같은 경우입니다. 나머지 3장의 카드는 어떤 카드와도 같지 않아야합니다.
2. 똑같은 숫자의 카드가 두 쌍이 있는 경우를 투페어(two pair)라고 합니다. 2,2,3,3,4 같은 경우입니다. 한장의 카드는 다른 어느 카드와도 같지 않아야합니다.
3. 똑같은 숫자의 카드가 3장이 있는 경우를 트리플(Three of a kind)이라고 합니다. 7,7,7,2,5와 같은 경우입니다. 나머지 두장의 카드는 어느 카드와도 같지 않아야합니다.
4. 똑같은 숫자의 카드가 3장이있고 나머지 두장의 카드의 숫자도 서로 같은 경우를 풀하우스(full house)라고 합니다. 원페어와 트리플이 합쳐진거라고 보면 되겠습니다.
5. 똑같은 숫자의 카드가 4장이 있는 경우를 포카드(four of a kind)라고 합니다.

선수에게 나눠준 카드 다섯장이 이 네가지중에 하나에 해당하는지를 따지는 코드를 만들어보겠습니다. 5장의 카드를 2장씩 비교해 숫자가 같은 경우가 나오면 이를 세는 방법을 이용하겠습니다. 5장의 카드에서 2장씩 비교하면 총 10번을 비교해야합니다.

$$_5C_2 = \frac{5!}{3!2!}=\frac{5\times4\times3\times2\times1}{(3\times2\times1)(2\times1)} =
\frac{5\times4}{2\times1} = 10$$

첫쨰 카드는 둘째, 셋째, 넷째, 다섯째 카드와 비교하고 (4번 비교), 둘째 카드는 셋째, 넷째, 다섯째 카드와 비교하고 (3번 비교), 셋째 카드는 넷째, 다섯째 카드와 비교하고 (2번 비교), 넷째 카드는 다섯째 카드와 비교하면 (1번비교) 모든 카드와 비교하게 됩니다. 4+3+2+1=10 총 10번 비교하는 겁니다.

이 과정을 코드로 옮기면 아래와 같습니다.

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
print(paircount)

paircount변수에는 총 몇개의 똑같은 쌍이 있는지에 대한 결과가 저장됩니다.

1. 원페어의 경우, paircount에는 1이 저장됩니다. 숫자가 똑같은 쌍이 하나밖에 없기 때문입니다.
2. 투페어의 경우, paircount에는 2가 저장됩니다. 숫자가 똑같은 쌍이 두개가 있기 때문입니다.
3. 트리플의 경우, paircount에는 3이 저장됩니다. 예를 들어, 2s, 2h, 2d, 3c, 4c 숫자뒤의 알파벳은 무늬를 표시합니다. 이 경우 똑같은 숫자의 쌍은 2s-2h, 2s-2d, 2h-2d 이렇게 3개가 됩니다.
4. 풀하우스의 경우, 트리플과 원페어가 같이 있으므로 3+1=4가 paircount에 저장됩니다.
5. 포카드의 경우, paircount에는 6이 저장됩니다. 예를 들어, 2s, 2h, 2d, 2c, 3c의 경우 똑같은 숫자의 쌍은 2s-2h, 2s-2d, 2s-2c, 2h-2d, 2h-2c, 2d-2c 이렇게 6개가 됩니다.

위의 코드로 총 5개의 패를 알아낼 수 있습니다.

숫자만으로 따질 수 있는 나머지 하나는 다섯장의 카드 숫자가 연속적으로 연결되는 경우입니다. 이를 스트레이트(straight)라고 합니다. 1(A),23,4,5인 경우도 있고, 9,10,11(J),12(Q),13(K)인 경우도 있습니다. 예외의 경우로 10,11(J),12(Q),13(K),1(A)도 있습니다. Stright인지 아닌지를 프로그램이로 알아내는 방법으로 카드의 다섯개를 숫자 순서대로 정렬해서 비교하는 방법이 있습니다.

먼저 1 pair, 2 pairs, trple, fulll house, four of a kind 어느 것도 아님을 먼저 확인합니다. 어느 것도 아닌 것을 확인했으면 다섯개 카드를 숫자별로 정렬합니다. 그런다음 가장 작은 숫자와 가장 큰 숫자의 차이가 4이면 straight입니다, [1,2,3,4,5], [2,3,4,5,6], …, [9,10,11,12,13]이 이 경우에 햐당합니다. [1, 10,11,12,13]의 경우도 straight인데 이 경우는 가장 작은 숫자는 1이고 두번째로 작은 숫자는 10인 경우입니다. 이를 파이썬 코드로 옮기면 다음과 같습니다.

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
print(num, straightox)

스트레이트면 straightox에 True가 저장되고 그렇지 않으면 False가 저장됩니다.

위의 코드도 짧게 줄여보겠습니다. 먼저 다섯줄에서 여섯째줄에 있는 2개의 if를 or를 써서 아래와 같이 다시 쓸 수 있습니다. 두 if중에 하나만 참이어도 참이기때문입니다. 이전에 if다음에 있었던 내용을 소괄호로 묶은 다음, 두개를 or 로 연결해서 하나의 if로 쓰면됩니다. 둘중의 하나만 참이어도 straightox에 True값이 저장됩니다.

num = [cards[k][1] for k in range(5)]
num.sort()
strightox = False
if paircount == 0:
    if ((num[4]-num[0]) == 4) or (num[0] == 1 and num[1] == 10):
      straightox = True
print(num, strightox)

한번 더 코드를 줄여보겠습니다. if블록안에 if불록 하나만 있으면, and로 연결해서 하나의 if로 합칠 수 있습니다. 둘다 다 참이어야만 참이기 떄문입니다. 이번에는 두내용 각각을 소괄호로 묶은 다음 두개를 and로 연결해서 하나의 if로 쓰는 겁니다.

num = [cards[k][1] for k in range(5)]
num.sort()
strightox = False
if (paircount == 0) and (((num[4]-num[0]) == 4) or (num[0] == 1 and num[1] == 10)):
    straightox = True
print(num, strightox)

이렇게 여러 if를 하나로 묶으면 코드에 사용되는 줄 수는 줄어들지만, 다른 사람이 코드를 읽으려하면 더 이해하기 어려워지는 단점이 있습니다. 이 때문에 여러개의 if를 그대로 쓸지 아니면, 합쳐서 최소 갯수의 if로만 쓸지는 잘 결정해야 합니다.

이제 플러쉬인지 아닌지 알아내는 코드를 만들어 보겠습니다. 이번에는 먼저 다섯장의 카드를 무늬로 정렬한 다음 첫번째 무늬와 마지막 다섯번쨰 무늬가 같은지를 비교하는 방법으로 플러쉬인지 확인할 수 있습니다. 무늬로 정렬한 카드의 경우, 첫번째 카드의 무늬와 마지막 카드의 무늬가 같으면 다섯장 모두 카드 무늬가 같기 때문입니다.

suit = [cards[k][0] for k in range(5)]
suit.sort()
flushox = False
if suit[0] == suit[4]:
    flushox = True
print(suit, flushox)

이제 paircount, straightox, flushox를 이용해 패의 순위를 결정하겠습니다.

1. 가장 높은 순위의 패는 스트레이트 플러쉬(straight flush)입니다. 스트레이트(straight)이면서 플러쉬(flush)인 패로 straightox 변수와 flushox변수가 모두 참(True)인 경우입니다. 스트레이트 플러쉬이면서 10, 11(J), 12(Q), 13(K), 1(A)인 경우를 로열 플러쉬(Rroyal flush)라고 해서 더 높게 쳐주기도 하는데, 여기서는 모두 같은 스트레이트 플러쉬로 하는 걸로 하겠습니다. 가장 높은 순위이므로 순위를 의미하는 rank변수에 1을 저장하겠습니다.
2. 두번째로 높은 순위의 패는 포카드(four of a kind)입니다. paircount가 6인 경우입니다. rank변수에는 2를 저장하겠습니다.
3. 세번째로 높은 순위의 패는 풀하우스(full house)입니다. paircount가 4인 경우입니다. rank변수에는 3을 저장하겠습니다.
4. 네번째로 높은 순위의 패는 플러쉬(flush)입니다. 스트레이트가 아닌 플러쉬로 flushox에는 True가 저장되어있고, straightox에는 False가 저장되어 있습니다. rank변수에는 4를 저장하겠습니다.
5. 다섯번째로 높은 순위의 패는 스트레이트(straight)입니다. 플러쉬가 아닌 스트레이트 straightox에는 True가 저장되어있고, flushox에는 False가 저장되어 있습니다. rank변수에는 5를 저장하겠습니다.
6. 여섯번째로 높은 순위의 패는 트리플(three of a kind)입니다. paircount가 3인 경우입니다. rank변수에는 6을 저장하겠습니다.
7. 그 다음 순위의 패는 투페어(two pair)입니다. paircount가 2인 경우입니다. rank변수에는 7을 저장하겠습니다.
8. 그 다음은 원페어(one pair)입니다. apircount가 1인 경우입니다. rank변수에는 8을 저장하겠습니다.
9. 그 외에 아무것도 아닌 패는 rank변수에 9를 저장하겠습니다.

위의 순위 결정 방법을 코드로 만들면 다음과 같습니다.

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
print(cards, rank)