Page 1 of 1

Cribbage Challenge

Posted: Thu Jul 14, 2011 12:49 pm
by fliptw
Well if you want a challenge:

Write something that calculates the score of a cribbage hand(not the crib), and with that, show the hands for the 3 highest scores possible.

this is an example of something that isn't a challenge.

Re: Descentbb.net forum challenge, for all users (proposal)

Posted: Thu Jul 14, 2011 2:19 pm
by Isaac
I think this could also include all possible size of hands.

2, 3, and 4 cards per hands. (I'm looking at the rules)
card values:
K = 10
Q = 10
J = 10
TEN = 10
NINE = 9
EIGHT = 8
...
skip...
...
TWO = 2
ACE = 1
Yeah, I wonder if I could do this in a single elegant python line...

Re: Descentbb.net forum challenge, for all users (proposal)

Posted: Thu Jul 14, 2011 2:51 pm
by fliptw
just keep it to 4 cards and the turn.

Re: Descentbb.net forum challenge, for all users (proposal)

Posted: Thu Jul 14, 2011 3:05 pm
by Isaac
ok.
edit:
1524 combinations of cards that score between 31 and 29... I know that's not the challenge, but that's a lot!
/edit

I started writing some code, but I misunderstood the procedure of the game. I'll have to start over later after rereading the rules.

Re: Descentbb.net forum challenge, for all users (proposal)

Posted: Thu Jul 14, 2011 8:34 pm
by Jeff250
The cribbage challenge looks interesting. I think I'm going to take a suboptimal stab at it tonight, once I understand the scoring rules...

Re: Descentbb.net forum challenge, for all users (proposal)

Posted: Thu Jul 14, 2011 11:21 pm
by Jeff250
To add to the above, Isaac, you don't need me to post challenges. Feel free to post your own and see who bites. ;)

Here's what I *think* is a correct scoring function:

Code: Select all

# coding: UTF-8
from itertools import chain, groupby, combinations

RANKS = u'A23456789TJQK'
SUITS = u'♥♦♣♠'
VALUES = dict((c, min(n + 1, 10)) for c, n in zip(RANKS, range(len(RANKS))))
INDEXES = dict((c, n) for c, n in zip(RANKS, range(len(RANKS))))

def count(iterable):
    return sum(1 for x in iterable)

def powerset(xs, min_len=0):
    return chain.from_iterable(combinations(xs, k)
                               for k in range(len(xs), min_len - 1, -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 v in powerset(values) if sum(v) == 15)
    # score two points for pairs, six for three of a kind, 12 for four
    score += sum(2 for t in combinations(ranks, 2) if t[0] == t[1])
    # score four points for a flush, five for including starter
    if all(x == y for x, y in zip(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
    runss = groupby(count(xs) for xs in powerset(indexes, 3)
                              if all(y - x == 1 for x, y in zip(xs, xs[1:])))
    try: key, runs = runss.next() # try to get set of longest runs
    except StopIteration: pass
    else: score += sum(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)

Re: Descentbb.net forum challenge, for all users (proposal)

Posted: Fri Jul 15, 2011 8:56 am
by Isaac

Code: Select all

cards={"K":10,"Q":10,"J":10,"10":10,"9":9,"8":8,"7":7,"6":6,"5":5,"4":4,"3":3,"2":2,"A":1}
hands=[(str((a[0],b[0],c[0],d[0])),(a[1]+b[1]+c[1]+d[1])) for a in cards.items() for b in cards.items() for c in cards.items() for d in cards.items() if a[1]+b[1]+c[1]+d[1]<31 and a[1]+b[1]+c[1]+d[1]>29]
print len(hands)
^---- That isn't anything.
This is how far I've got, but I don't get why there's pegs in the game. I need to find more clear instructions, but I have lots of homework for the next few days (I didn't realize there would be a challenge posted so quickly).

Re: Descentbb.net forum challenge, for all users (proposal)

Posted: Fri Jul 15, 2011 10:34 am
by fliptw
it has pegs to keep score. The challenge concerns the counting at the end of a round(the show).

basically score according to this:
wikipedia wrote:
  • fifteen-twos
    • two points for each separate combination of two or more cards totalling exactly fifteen
  • runs
    • three points for a run of three consecutive cards (regardless of suit)
    • four points for completing a run of four
    • five points for completing a run of five
  • pairs
    • two points for a pair of cards of a kind
    • six points for three cards of a kind (known as a "pair royal", comprising three distinct pairs)
    • twelve points for four cards of a kind (a "double pair royal", comprising six distinct pairs)
  • flush
    • four points for a flush, where all four cards in the hand are of the same suit, with an additional point if the starter card is also of that suit. It is only a flush in the crib if all five cards, the four in the crib and the starter card, are of the same suit.
  • nobs
    • one point for holding the Jack of the same suit as the starter card

Re: Descentbb.net forum challenge, for all users (proposal)

Posted: Fri Jul 15, 2011 11:42 am
by Isaac
So these are additional points on top of the points calculated by each card. I see.

Re: Descentbb.net forum challenge, for all users (proposal)

Posted: Fri Jul 15, 2011 12:52 pm
by fliptw
No, only score using that list. you are over-thinking the problem Issac.

Re: Descentbb.net forum challenge, for all users (proposal)

Posted: Fri Jul 15, 2011 3:54 pm
by Jeff250
One observation that I made for my solution is that the six points for the three of a kind can be counted as just two points for each of the three pairs in the three of a kind. A similar observation can be made for four of a kind. So if it simplifies, you only need to count distinct pairs.

My code for scoring runs though is pretty messy. I think I'm failing to see some kind of elegant simplification.

Re: Descentbb.net forum challenge, for all users (proposal)

Posted: Fri Jul 15, 2011 4:09 pm
by fliptw
you can use the number of pairs to limit the length of a given run. That would technically not simplify the solution.

Re: Descentbb.net forum challenge, for all users (proposal)

Posted: Fri Jul 15, 2011 8:07 pm
by Jeff250
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: Select all

# 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.

Re: Descentbb.net forum challenge, for all users (proposal)

Posted: Sat Jul 16, 2011 7:58 am
by Isaac
I think I finally understand the instructions of the challenge, now that I've watched a few games and read different versions of the instructions.
(remember Isaac is slow)

I'm going to first do the UI (click the cards to change them):
http://testing.isaacg.net/crib/cirb.html <-- the first sprite sheet I ever used in a program. :E
It will allow the user to not only declare what hand he/she has, but there will be another way (not yet added) of checking off cards that have been eliminated from the probability of being obtained by the holder.

1: Show the value and type of hand the holder has.
2: Show the 3 highest possible hands of the cards remaining in the deck, less the eliminated cards.


Am I on track? I assume I finally am.

Re: Descentbb.net forum challenge, for all users (proposal)

Posted: Sat Jul 16, 2011 8:34 pm
by Jeff250
I think it's far outside the scope of the challenge as stated, but if that seems interesting to you, why not.

Re: Descentbb.net forum challenge, for all users (proposal)

Posted: Sun Jul 17, 2011 1:27 am
by Jeff250
Also, looking over your code, to create an iterator for every possible hand, simply:

Code: Select all

from itertools import combinations
hands = combinations(cards.items(), 5)
Substitute 5 with 4 to not include the starter. This also handles the problem of getting duplicate cards, but you'll also need to distinguish cards by their suit for this to work right. Something like this should work (untested):

Code: Select all

from itertools import product
cards = list(product(ranks, suits))

Re: Descentbb.net forum challenge, for all users (proposal)

Posted: Sun Jul 17, 2011 6:21 am
by Isaac
interesting. I have not yet tried product() or combinations() before. I ended up having to add list() around each of those to make them work. Very cool stuff in itertools! Thanks.

Re: Descentbb.net forum challenge, for all users (proposal)

Posted: Sun Jul 17, 2011 7:26 am
by Jeff250
So list() will put everything into memory all at once, as opposed to just being an iterator generating the items lazily. This is fine for small sequences like a list of all possible cards that you'll be using over and over again, but you should think twice for using list() with all 52 choose 5 possible hands. Iterators don't support all of the operations that a list does (like len()), but this shouldn't be a problem.

Re: Descentbb.net forum challenge, for all users (proposal)

Posted: Sun Jul 17, 2011 7:30 am
by Isaac
But with out list() it only can return <itertools.combinations object at 0xb76aaa04> .

Maybe I'm not following the pydoc correctly...

Re: Descentbb.net forum challenge, for all users (proposal)

Posted: Sun Jul 17, 2011 8:07 am
by Jeff250
That's normal. It can't print out the elements because doing so would exhaust the iterator, making it useless thereafter. Also, iterators can be infinite, which can take a while to print. You don't ever want to call list() on an infinite iterator either unless you're prepared for some thrashing. In the past, I might have done this once or twice on accident. ;)

A common way to use an iterator is something like:

Code: Select all

for hand in hands:
    pass # your code here
Or in this case, maybe (but probably not):

Code: Select all

for a, b, c, d, e in hands:
    pass # your code here
Just remember, whenever you want to reiterate freshly over all the hands, you'll need to recreate the iterator by calling combinations(). This might seem like a disadvantage, but it's really not--unlike lists, iterators are cheap to create, since all of the work is done lazily while iterating.

Most library functions that you might think take lists are actually taking iterators, lists being iterable.

Re: Descentbb.net forum challenge, for all users (proposal)

Posted: Tue Jul 19, 2011 8:17 am
by snoopy
I'd take a stab at challenges, when I had time.

I'd also fall relatively short of the coding prowess present in the forums here.