最近在写一个德州扑克的逻辑, 抽象出问题就是: 给出2张私有牌以及5张公共牌,
请问如何找到最大的牌型组合.
德州扑克牌型介绍
德州扑克的牌型从大到小排列如下:
- 皇家同花顺:同花色的A、K、Q、J、10。
- 同花顺:五张同花色的连续牌。
- 四条:四张是相同点数的牌,第五张是随意的一张牌。
- 葫芦:三张相同点数及任何两张其他相同点数的扑克牌组成。
- 同花:五张不按顺序但花色相同的牌组成。
- 顺子:五张顺序扑克牌组成。
- 三条:三张相同点数和两张不同点数的牌组成。
- 两对:两对点数相同但两两不同的扑克和随意的一张牌组成。
- 对子:两张点相同的牌。
- 高牌:既不是同一花色也不是同一点数的五张牌组成。
解决思路
一种比较直观的解法就是穷举所有的可能性,然后从中找到一个最大牌型,但是这种方法并非最优解.
另外一种方法就是根据牌型从大到小去判断能否由 2+3 组成.
我使用第二种思路来解决问题.
首先, 我们将同花这种情况独立出来, 根据私有牌组成可以判断其是否有可能组成同花.
然后, 观察两张私有牌, 然后得出其升级路线:
两张私有牌之差为(0, 5), 判断其是否可能为: 皇家同花顺/同花顺/同花/顺子,
因为只要是前面三种, 那么四条/葫芦是不可能出现的, 而且如果是顺子, 而且判断可能出现同花,
那么可以得出最大牌型为同花两张私有牌相等, 判断其是否可能为: 四条/葫芦/同花/三条/两对/对子,
为了加快我们的判断过程, 可以在算法启动时对公共牌进行特征分类,
一个是其每个牌值的出现次数统计, 另外一个就是对颜色的统计其它情况, 判断其是否可能为: 四条/葫芦/同花/三条/两对/对子/高牌
需要注意的是, 路线一和路线二是互斥的, 而路线二不会到达路线三.
代码
class ResultType(object):
HighCard = 1 # 高牌
SinglePair = 2 # 对子
TwoPair = 3 # 两对
Three = 4 # 三条
Sequence = 5 # 顺子
SameColor = 6 # 同花
Gourd = 7 # 葫芦
Four = 8 # 四条
Flush = 9 # 同花顺
RoyalFlush = 10 # 皇家同花顺
ResultTypeStr = ('', '高牌', '对子', '两对', '三条', '顺子', '同花', '葫芦', '四条', '同花顺', '皇家同花顺')
class TaxasSolution(object):
def __init__(self, t, w):
self.result_type = t
self.origin = w
factor = (0, 4, 8, 12, 16)
if isinstance(w, list):
s = 0
for i, www in enumerate(reversed(w)):
s += (www << factor[i])
self.weight = s
else:
self.weight = w
def __lt__(self, other):
if self.result_type != other.result_type:
return self.result_type - other.result_type < 0
return self.weight - other.weight < 0
def __gt__(self, other):
if self.result_type != other.result_type:
return self.result_type - other.result_type > 0
return self.weight - other.weight > 0
def __eq__(self, other):
if self.result_type != other.result_type:
return False
return self.weight == other.weight
def __ne__(self, other):
if self.result_type != other.result_type:
return True
return self.weight != other.weight
def __str__(self):
out = [
'weight is {0}'.format(self.weight),
'牌型为:{0}'.format(ResultTypeStr[self.result_type]),
'组合为:{0}'.format(self.origin)
]
return '\n'.join(out)
class TaxasPlayer(CardPlayer):
def __init__(self, cds, pub_attr):
self.pub = pub_attr
self.min = cds[0].value
self.max = cds[1].value
self.unique_type = None
super(TaxasPlayer, self).__init__(cds)
def init_data(self):
if self.min == 1:
self.min = 14
if self.max == 1:
self.max = 14
if self.min > self.max:
self.min, self.max = self.max, self.min
if self.cards[0].type == self.cards[1].type:
self.unique_type = self.cards[0].type
def find_best_solution(self):
sc = None
if self.may_same_color() and self.pub.types_count[self.unique_type] > 2:
w = [self.max, self.min] + self.pub.get_same_type_values(self.unique_type)
w.sort(reverse=True)
sc = TaxasSolution(ResultType.SameColor, w)
solution = self.step_1(sc)
if not solution:
solution = self.step_2(sc)
if not solution:
solution = self.step_3(sc)
return solution
def may_seq(self):
return 0 < self.max - self.min < 5
def may_pair(self):
return self.max == self.min
def may_same_color(self):
return self.unique_type is not None
def step_1(self, may_same_color):
# 判断是否有顺子
if self.may_seq():
start, end = self.get_value_to_seq(self.min, self.max, 2, 14)
seq_max = None
while True:
can_be_seq = True
can_be_same_color = True
for i in range(end - 4, end + 1):
if i == self.min or i == self.max:
continue
if i not in self.pub.values:
can_be_seq = False
break
if can_be_same_color and may_same_color:
can_be_same_color = self.unique_type in self.pub.value_types[i]
if can_be_seq:
if not may_same_color:
return TaxasSolution(ResultType.Sequence, end)
if can_be_same_color:
return TaxasSolution(ResultType.Flush, end) if \
end <= 2="" cardnum.card_k="" else="" taxassolution(resulttype.royalflush,="" 0)="" seq_max="seq_max" or="" end="" -="1" if="" start="" <="" 4:="" break="" seq_max:="" return="" may_same_color="" taxassolution(resulttype.sequence,="" seq_max)="" none="" def="" step_2(self,="" may_same_color):="" #="" prv:="" a="" 判断牌型="" self.may_pair():="" val="self.max" pub_val_cnt="self.pub.values.get(val," val_sum="pub_val_cnt" +="" 先判断是不是四条="" aaaab="" taxassolution(resulttype.four,="" (val="" <<="" 4)="" self.pub.max_except(val))="" solution="None" 再判断是不是葫芦:="" 要求="" 起码有三个相同的="" tmp="self.pub.values.items()" len(self.pub.values)="" 5:="" for="" card_value,="" card_count="" in="" tmp:="" card_value="=" val:="" continue="">= 3:
if val_sum == 3:
s = TaxasSolution(
ResultType.Gourd, (card_value << 4) + val if card_value > val \
else (val << 4) + card_value)
else:
return TaxasSolution(ResultType.Gourd, (card_value << 4) + val)
elif card_count == 2 and val_sum == 3:
s = TaxasSolution(ResultType.Gourd, (val << 4) + card_value)
else:
continue
solution = self.choice_solution(s, solution) if solution else s
if solution:
return solution
# 判断是不是同花
if may_same_color:
return may_same_color
# 判断是不是三条
if val_sum == 3:
return TaxasSolution(ResultType.Three, [val] + self.pub.max_except(val, 2))
# 判断是不是两对
for card_value, card_count in tmp:
if card_count == 2:
s = TaxasSolution(ResultType.TwoPair,
[val, card_value, self.pub.max_except(card_value)] if val > card_value \
else [card_value, val, self.pub.max_except(card_value)])
solution = self.choice_solution(s, solution) if solution else s
if solution:
return solution
# 肯定是对子
return TaxasSolution(ResultType.SinglePair, [val] + self.pub.max_n(3))
def step_3(self, may_same_color):
# 四条
prv_cards = (self.min, self.max)
tmp = self.pub.values.items()
val_2 = val_3 = None
for card_val, card_count in tmp:
if card_count == 3:
if card_val in prv_cards:
x = prv_cards[0] if card_val == prv_cards[1] else prv_cards[1]
return TaxasSolution(ResultType.Four, (card_val << 4) + x)
else:
val_3 = card_val
elif card_count == 2:
if not val_2:
val_2 = card_val
elif val_2 < card_val:
val_2 = card_val
# 葫芦
x, y = self.pub.values.get(self.min, 0) + 1, self.pub.values.get(self.max, 0) + 1
if x == 2 and y == 3:
return TaxasSolution(ResultType.Gourd, (self.max << 4) + self.min)
elif x == 3 and y == 2:
return TaxasSolution(ResultType.Gourd, (self.min << 4) + self.max)
elif x == 3 and y == 3:
return TaxasSolution(ResultType.Gourd, (self.max << 4) + self.min)
if may_same_color:
return may_same_color
# 三条
if x == 3:
other = self.pub.max_except(self.min)
return TaxasSolution(ResultType.Three,
[self.min, other, self.max] if other > self.max else [self.min, self.max, other])
elif y == 3:
other = self.pub.max_except(self.max)
return TaxasSolution(ResultType.Three,
[self.max, other, self.min] if other > self.min else [self.max, self.min, other])
elif val_3:
return TaxasSolution(ResultType.Three, (val_3 << 8) + (self.max << 4) + self.min)
# 两对
if x == 2 and y == 2:
return TaxasSolution(
ResultType.TwoPair, (self.max << 8) + (self.min << 4) + self.pub.max_except_some(self.max, self.min))
elif val_2:
if x == 2:
l, m = (self.min, val_2) if val_2 < self.min else (val_2, self.min)
r = self.max
return TaxasSolution(ResultType.TwoPair, (l << 8) + (m << 4) + r)
elif y == 2:
l, m = (self.max, val_2) if val_2 < self.max else (val_2, self.max)
r = self.min
return TaxasSolution(ResultType.TwoPair, (l << 8) + (m << 4) + r)
# 对子
if x == 2:
weight = [self.max] + self.pub.max_except(self.min, 2)
weight.sort(reverse=True)
return TaxasSolution(ResultType.SinglePair, [self.min] + weight)
elif y == 2:
weight = [self.min] + self.pub.max_except(self.max, 2)
weight.sort(reverse=True)
return TaxasSolution(ResultType.SinglePair, [self.max] + weight)
elif val_2:
weight = [self.min, self.max, self.pub.max_except(val_2)]
weight.sort(reverse=True)
return TaxasSolution(ResultType.SinglePair, [val_2] + weight)
weight = [self.max, self.min] + self.pub.max_n(3)
weight.sort(reverse=True)
return TaxasSolution(ResultType.HighCard, weight)
@staticmethod
def choice_solution(a, b):
if a.result_type == b.result_type:
return a if a.weight > b.weight else b
return a if a.result_type > b.result_type else b
@staticmethod
def get_value_to_seq(num1, num2, left, right):
'''注意这里的A的值已经是14了'''
if num1 > num2:
num1, num2 = num2, num1
diff = 5 - num2 + num1 - 1
s = num1 - diff
s = s < left and left or s
e = num2 + diff
e = e > right and right or e
return s, e
def __str__(self):
card_type_str = ('', '黑桃', '红桃', '方块', '草花', '小王', '大王')
out = []
for card in self.cards:
out.append('%s_%d' % (card_type_str[card.type], card.value))
return ','.join(out)
class PubCardsAttr(object):
def __init__(self, cds):
self.values = {}
self.value_types = {}
self.sorted_values = []
self.types_count = [0] * 7
self.cards = cds
for card in self.cards:
t, v = card.type, card.value
if v == 1:
v = 14
self.types_count[t] += 1
if v < CardNum.Card_2:
v = 14
self.values[v] = self.values.get(v, 0) + 1
self.sorted_values.append(v)
if v not in self.value_types:
self.value_types[v] = set()
self.value_types[v].add(t)
self.sorted_values.sort(reverse=True)
@staticmethod
def cmp_card(a, b):
av = a.value if a.value > 1 else 14
bv = b.value if b.value > 1 else 14
return av - bv
def max_n(self, n):
return self.sorted_values[:n]
def max_except(self, n, wanted=None):
out = []
for i in self.sorted_values:
if i != n:
if not wanted:
return i
out.append(i)
if len(out) == wanted:
return out
raise Exception('should not be here')
def max_except_some(self, *args):
for i in self.sorted_values:
if i not in args:
return i
raise Exception('should not be here.')
def get_same_type_values(self, tar):
self.cards.sort(cmp=self.cmp_card, reverse=True)
out = []
for card in self.cards:
if card.type == tar:
if card.value == 1:
out.append(14)
else:
out.append(card.value)
if len(out) == 3:
break
return out
def __str__(self):
card_type_str = ('', '黑桃', '红桃', '方块', '草花', '小王', '大王')
out = []
for card in self.cards:
out.append('%s_%d' % (card_type_str[card.type], card.value))
return ','.join(out)
=>
测试
在完成后, 需要使用完备的测试对其进行测试, 用例就不在这赘述了.
代码不是很完善, 还有很多可以改善的地方.