프로젝트 오일러 054

포커 게임에서 이긴 횟수 구하기

문제

54번 문제
포커 게임에서 이긴 횟수 구하기

점수 체계 정하기

포커의 승패는 다섯장의 카드로 만들 수 있는 계급의 높낮이로 승부를 가르며, 계급이 같은 경우, 더 높은 숫자를 가진 사람이 이기는 규칙이 있습니다. 문제에서는 각 계급이 어떻게 결정되는지를 정의해두었으니, 규칙과 높은 카드를 비교하여 높은 쪽이 승리하도록 한 판의 게임을 판정하는 로직을 구현하면 됩니다.

그런데 아무래도 판단해야 하는 규칙이 많고 복잡하다보니, 계급을 점수로 환산하려고 합니다. 그런데 Two Pair는 두 개의 One Pair이고, Full House는 One Pair + Three of a Kind 의 조합입니다. 따라서 T/P의 점수는 O/P의 점수의 두 배가, F/H의 점수는 O/P + T/K의 점수가 되도록 점수 체계를 구성한다면, 이 두 케이스의 계급을 판정하는 로직은 구현할 필요가 없습니다.

계급의 중간쯤에 있는 Flush를 100점으로 둔다면 다음과 같이 계급별 점수를 정할 수 있을 겁니다. 이 점수 체계는 상대적인 것이므로 보다 더 적절한 체계를 사용해도 좋습니다. 다른 계급 두 개의 조합이 동시에 성립하는 계급의 점수가 이를 구성하는 계급들의 점수의 합이 되고, 전체 계급의 순서가 지켜질 수 있도록만 설정하면 됩니다.

  • One Pair = 25
  • Two Pairs = 50 (25 × 2)
  • Three of a Kind = 80
  • Straight = 90
  • Flush = 100
  • Full House = 105 (25 + 80)
  • Four of a Kind = 110
  • Straight Flush = 190 (90 + 100)
  • Royal Flush = 200

판정

점수 체계를 위와 같이 구성하면 계급 판정에서 불필요한 판정을 계산할 필요가 없습니다. 아래와 같은 순서대로 판정합니다.

  1. 플러시 조건을 만족한다면 점수에 100점을 더합니다.
    1. 로열 플러시 조건이라면 100점을 더하고 종료합니다.
  2. (플러시 여부와 상관없이) 스트레이트 조건을 만족한다면 점수에 90점을 더합니다.
  3. F/K 조건을 만족한다면 110점을 더합니다.
  4. T/K 조건을 만족한다면 80점을 더합니다.
  5. One Pair를 이루는 쌍이 몇 개인지 찾고, 각 쌍마다 25점을 더합니다.

하이 카드를 통해서 비교하기 위해서는 5장의 카드에서 숫자의 개수가 많고, 숫자가 더 큰 순서대로 정렬하여 5개를 나열합니다. 어차피 하이 카드는 같은 계급(점수)에서만 비교하므로, 이렇게 정렬하면, 계급과 그 계급의 숫자가 같은 (예를 들어 두 사람이 모두 5 One Pair 라면) 경우에 남은 카드로 승부를 결정하게 됩니다.

참고로 주의할 점은, A는 K보다 높은 카드이지 1이 아닙니다. 따라서 Q, K, A, 2, 3 은 스트레이트가 아니라는 점입니다.

from utils import streamReader
from euler import elapsed


def evaluate(cards: list[tuple[int, str]]) -> tuple[int, list[int]]:
    score = 0
    d = dict()
    for n, _ in cards:
        d[n] = d.get(n, 0) + 1
    ns = sum(
        [
            [x] * y
            for x, y in sorted(d.items(), key=lambda x: (x[1], x[0]), reverse=True)
        ],
        [],
    )

    # flush
    if all(card[1] == cards[0][1] for card in cards):
        score += 80
        if ns == list(range(14, 9, -1)):
            return (200, ns)

    # straight
    if all(x - y == 1 for (x, y) in zip(ns, ns[1:])):
        score += 75

    # kinds
    if any(v == 4 for v in d.values()):
        score += 100
    if any(v == 3 for v in d.values()):
        score += 70

    score += sum(25 for v in d.values() if v == 2)

    return score, ns


def main():
    ps = {c: i for (i, c) in enumerate(",.23456789TJQKA")}
    res = 0
    url = "https://euler.synap.co.kr/files/poker.txt"
    for line in streamReader(url):
        # if not line.strip():
            # continue
        cards = [(ps[x[0]], x[1]) for x in line.split()]
        a, b = cards[:5], cards[5:]
        x, y = map(evaluate, (a, b))
        if x > y:
            res += 1
    print(res)


if __name__ == "__main__":
    main()

이 풀이에서 사용된 streamReader는 다른 글에서 소개한 바 있습니다. urlopen 함수가 리턴하는 http.client.HTTPResponse 객체역시 같은 i/o 프로토콜을 지원하므로 동일하게 적용할 수 있습니다.