OK, I had some time to optimize the "n-of-a-kind" scoring, and I think that the "runs" scoring is faster and easier to read, although I still think it could somehow be simpler:
Code:
# coding: UTF-8
from itertools import count, izip, chain, groupby, combinations
RANKS = u'A23456789TJQK'
SUITS = u'♥♦♣♠'
VALUES = dict((c, min(n + 1, 10)) for c, n in izip(RANKS, range(len(RANKS))))
INDEXES = dict((c, n) for c, n in izip(RANKS, range(len(RANKS))))
def powerset(xs, min_len=0):
return chain.from_iterable(
combinations(xs, k) for k in range(len(xs), min_len - 1, -1))
def product(xs):
return reduce(lambda x, y: x * y, xs, 1)
def rank(card):
return card[0]
def suit(card):
return card[1]
def score(*hand):
starter = hand[-1]
ranks = sorted(rank(card) for card in hand)
suits = sorted(suit(card) for card in hand)
values = sorted(VALUES[rank] for rank in ranks)
indexes = sorted(INDEXES[rank] for rank in ranks)
# score two points for any combination adding up to 15
score = sum(2 for xs in powerset(values) if sum(xs) == 15)
# score two points for pairs, six for three of a kind, 12 for four
score += sum((0, 0, 2, 6, 12)[len(list(xs))] for k, xs in groupby(ranks))
# score four points for a flush, five for including starter
if all(x == y for x, y in izip(suits[:-1], suits[1:-1])):
score += 5 if suit(starter) == suits[0] else 4
# score one point if hand contains jack of same suit as starter
score += sum(1 for card in hand[:-1]
if rank(card) == 'J' and suit(card) == suit(starter))
# score n for each disjoint run of n cards, n >= 3
gs = ((i - j, list(xs)) for (i, xs), j in izip(groupby(indexes), count()))
runs = filter(lambda xs: len(xs) >= 3, # keep only a run of size >= 3
(list(xs) for i, xs in groupby(gs, lambda xs: xs[0])))
score += sum(len(r) * product(len(rank) for i, rank in r) for r in runs)
return score
assert(score(u'3♠', u'4♦', u'5♦', u'J♦', u'6♦') == 9)
assert(score(u'5♣', u'5♦', u'5♥', u'J♠', u'5♠') == 29)
assert(score(u'3♠', u'3♦', u'3♣', u'9♦', u'6♦') == 16)
assert(score(u'6♦', u'7♠', u'7♣', u'8♣', u'8♦') == 24)
assert(score(u'2♦', u'3♣', u'4♠', u'4♣', u'4♥') == 17)
edit: Simplified "runs" scoring by another line.