在紙牌遊戲中,一手包含五張牌而且每一手都有本身的排序,從低到高的順序以下:python
- 大牌:牌面數字最大
- 一對:兩張牌有一樣的數字
- 兩對:兩個不一樣的一對
- 三條:三張牌有一樣的數字
- 順子:全部五張牌的數字是連續的
- 同花:全部五張牌有一樣的花色
- 船牌:三張一樣數字的牌加一個一對
- 四條:四張牌有一樣的數字
- 同花順:牌面數字連續且有一樣的花色
- 皇家同花順:由\(10,J,Q,K,A\)五張牌構成的同花順
十三張牌根據牌面數字從小到大排序爲:\(2, 3, 4, 5, 6, 7, 8, 9, 10, J, Q, K, A\)數據結構
若是兩個玩家所持的一手牌排序相同,那麼牌面數字較大的獲勝。好比,一對八要大於一對五(見下面例一)。可是若是兩手牌排序打平,好比兩個玩家都有一對\(Q\),那麼比較剩下的牌中的最大者(見下面例四)。若是這張牌仍然打平,則比較其它剩下的牌中的最大者,依次類推。ui
考慮兩個玩家所持的下面五手牌,觀察他們的勝負狀況:spa
文本文件poker.txt中包含一千條隨機生成的兩個玩家的牌面數據,每一行包含十張牌(用一個空格分開),前五張牌是第一個玩家的,後五張牌是第二個玩家的。你能夠認爲全部牌都是有效的,每一個玩家所持的牌沒有特定的順序,而且在每一手牌中總有一個肯定的獲勝者。求在這一千手牌中玩家一獲勝了多少次?.net
分析:這是咱們到目前爲止看到的文字最多的題目,並且我爲了解決這個題目所寫的代碼也是到目前行數最多的。和之前的題目不一樣,這道題目的難點並不在於其中的數學原理,而是要對這個遊戲的規則自己有清楚的瞭解,而且可以把這些規劃用代碼表示出來。題目自己介紹規則的介紹不是很清楚,我建議你們參考這篇維基百科,裏面對這個遊戲的規則有更透徹清晰的介紹。總結起來遊戲的規則有這麼幾條:第一,每一手牌有九個可能的排序(皇家同花順就是同花順,因此不用單獨考慮),排序高的牌大於排序低的牌;第二,若是兩手牌的排序相同,則須要根據這一手牌的不一樣類型來進行排序,總結起來也有兩種狀況:(1)若是類型中不涉及對子以及各類條牌,也就是同花順、同花、順子以及大牌這四種類型時,只須要對牌面數字造成的列表從大到小排序,而後比較兩個列表便可。由於比較列表時,它會從第一個數字陸續向後比較,所以和這四種類型的牌的比較方式相同;(2)剩下五種涉及到對子和條牌的類型,也即四條、三條、船牌、二對和一對,則須要首先按條或者對分組,而後對條或者對中的牌面數字分別比較大小,這裏能夠用到python中Counter這個數據結構。code
因爲解題過程涉及大量的邏輯判斷,爲了讓代碼結構更加清晰,我使用了面向對象的方式來組織代碼。我編寫了兩個類:第一類是紙牌類,它有兩個屬性,分別是花色與牌面數字,爲了讓以後的比較更加方便,我把\(T,J,Q,K,A\)五個牌面分別映射至對應的數字。第二個類表示五張牌造成的一手,這個類有八個屬性,分別表示五張牌的牌面數字、五張牌的花色、五張牌牌面數字的分組統計、第一個分組的牌面數字、第二個分組的牌面數字、五張牌花色的類型數量、五張牌面數字從大到小排序的列表以及五張牌從大到小排序後,先後兩個數字之差構成的集合。此外,這個類有兩個方法:第一個方法用來判斷這一手牌的類型,返回每一手牌的類型名稱以及類型排序;第二個方法用來比較這一手牌和另外一手牌的大小,使用的是咱們前面總結的比較各手之間大小的規則。對象
最後,咱們從文本文件中導入數據,把每一行數據拆分爲第一個玩家和第二個玩家,並記錄第一個玩家獲勝的次數,最後返回這個次數即爲題目所求。代碼以下:blog
# time cost = 42.1 ms ± 157 µs from collections import Counter class Card: def __init__(self,vs): d = {'T':10,'J':11,'Q':12,'K':13,'A':14} self.s = vs[1] if vs[0] not in set('TJQKA'): self.v = int(vs[0]) else: self.v = d[vs[0]] class Hand: def __init__(self,cards): self.values = [x.v for x in cards] self.suits = [x.s for x in cards] self.value_counter = Counter(self.values).most_common() self.fc = self.value_counter[0][1] self.sc = self.value_counter[1][1] self.suit_kind = len(set(self.suits)) self.ranks = sorted([x.v for x in cards],reverse=True) self.diff = set([self.ranks[:-1][i]-self.ranks[1:][i] for i in range(4)]) def categories(self): if self.suit_kind == 1 and self.diff == {1}: return ('Straight Flush',9) elif self.suit_kind == 1: return ('Flush',6) elif self.diff == {1}: return ('Straight',5) elif self.fc == 4: return ('Four of a Kind',8) elif self.fc == 3 and self.sc == 2: return ('Full House',7) elif self.fc == 3 and self.sc == 1: return ('Three of a Kind',4) elif self.fc == 2 and self.sc == 2: return ('Two Pairs',3) elif len(self.value_counter) == 4 and self.fc == 2: return ('One Pair',2) else: return ('High Card',1) def is_winner(self,hand): if self.categories()[1] > hand.categories()[1]: return True elif self.categories()[1] < hand.categories()[1]: return False elif self.categories()[1] in [8,7,4,3,2]: return self.value_counter > hand.value_counter else: return self.ranks > hand.ranks def main(): count = 0 with open('data/ep54.txt') as f: hands = [line.split() for line in f] for hand in hands: p1_cards = [Card(x) for x in hand[:5]] p2_cards = [Card(x) for x in hand[5:]] p1_hand,p2_hand = Hand(p1_cards),Hand(p2_cards) if p1_hand.is_winner(p2_hand): count += 1 return count