28
\$\begingroup\$

This challenge is to score a Cribbage hand. If you don't play Cribbage, you've got some learning to do. We play with a standard poker deck, and a hand consists of four cards plus the "up card". There are two types of hand: normal, and a 'crib hand'.

Cards come in the format vs where v is one of: A23456789TJQK (T for ten) and s is one of SCDH. A hand will be given in the form (for example)

AS 2D 3H JS | 4S

where 4S is the up card. A crib hand will have the format

JD 3C 4H 5H | 5S !

Face cards have a value of 10, and the ace has a value of 1. Scoring is performed as follows.

  • Fifteens: for each subset of five cards whose sum is 15, add two points.
  • Pairs: for each pair of cards with the same rank (not value), add two points.
  • Runs: for each maximal run of consecutive cards of length longer than 2, add the length of the run in points.
  • Flush: if all five cards are the same suit, add five points. Otherwise, if all but the up card are the same suit, add four points. If this is a crib hand, the four-point variant is not counted.
  • Nobs: if there is a jack in hand with the same suit of the up card, add one point.

Notes:

  • Triples and fours of a kind are not special -- there are three pairs in a triple, so a triple is worth 6 points.

  • Runs can overlap. For example, AS AH 2D 3C | 2C (a double double run) has four runs of length 3 and two pair, so is worth 3+3+3+3+2+2 = 16 points.

  • Only maximal runs are counted, so KS QD JD TC | 9S is worth 5 points, since it is a run of 5. The sub-runs are not counted.

House Rule:

It's impossible to score 19 points in a hand. Instead of zero, report a score of 19.

Examples:

5S 5H 5D JS | KS
21

AS 2D 3H JS | 4S !
9

JD 3C 4H 5H | 5S
12

9S 8S 7S 6S | 5H !
9

9S 8S 7S 6S | 5H
13

8D 7D 6D 5D | 4D !
14

8D 7D 6D 5D | 4D
14

AD KD 3C QD | 6D
19

This is code golf. Shortest solution wins.

\$\endgroup\$
11
  • 4
    \$\begingroup\$ "fifteen two, fifteen four, ..." Aye, yai, yai, but it's been a while. \$\endgroup\$ Commented Apr 14, 2012 at 17:32
  • 3
    \$\begingroup\$ @dmckee, yes, I was very nice in my requirements for scoring. I thought about putting up a full description of the patter... "fifteen two, fifteen four, and a pair for six; peg them sticks". But then, the problem description would be 30 pages long. \$\endgroup\$
    – boothby
    Commented Apr 15, 2012 at 2:49
  • 1
    \$\begingroup\$ 3+3+3+3+2 = 16? I think you're missing another +2. \$\endgroup\$
    – grc
    Commented Apr 15, 2012 at 3:36
  • 1
    \$\begingroup\$ And are the second and last examples meant to be 9 and 1? I think the last rule should apply to them, but I'm not sure (never played cribbage before). \$\endgroup\$
    – grc
    Commented Apr 15, 2012 at 5:03
  • 1
    \$\begingroup\$ @grc yup, I missed the nobs on those. This is the only game I know in which scoring is the hardest part. \$\endgroup\$
    – boothby
    Commented Apr 15, 2012 at 5:26

7 Answers 7

7
\$\begingroup\$

C, 364 388 chars

It's big and ugly (though not as big as it once was):

char*L="CA23456789TJQKDHS",b[20],p[15],r[5],s[5],v,i=4,t,m,q;
g(j){++p[r[i]=strchr(L,b[j])-L];s[i]=strchr(L,b[j+1])-L;}
f(j,u){u==15?v+=2:++j<5&&f(j,u,f(j,u+(r[j]>9?10:r[j])));}
main(){gets(b);for(g(14);i--;r[i]^11|s[i]^s[4]||++v)g(i*3);
for(f(i,0);++i<15;v+=q?q*q-q:t>2?t*m:0,t=q?t+1:0,m=q?m*q:1)q=p[i];
while(++t<5&&s[t]==*s);v+=t>4-!b[16]?t:0;printf("%d\n",v?v:19);}

(Line breaks were added to make it easier to read; those aren't included in the above tally.)

The problem description didn't specify if the code needed to check for invalid input, so naturally I assumed that the program was free to misbehave at will if the input, say, contained extra whitespace.

Here's the ungolfed version:

#include <stdio.h>
#include <string.h>

/* A-K correspond to values 1-13. Suit values are arbitrary.
 */
static char const *symbols="CA23456789TJQKDHS";

/* Used as both an input buffer and to bucket cards by rank.
 */
static char buf[20];

/* The cards.
 */
static int rank[5], suit[5];

/* The cards broken down by rank.
 */
static int buckets[15];

static int score;
static int touching, matching, i;

/* Read card number i from buf at position j.
 */
static void getcard(int j)
{
    rank[i] = strchr(symbols, buf[j]) - symbols;
    suit[i] = strchr(symbols, buf[j+1]) - symbols;
    ++buckets[rank[i];
}

/* Recursively find all combinations that add up to fifteen.
 */
static void fifteens(int j, int total)
{
    for ( ; j < 5 ; ++j) {
        int subtotal = total + (rank[j] > 9 ? 10 : rank[j]);
        if (subtotal == 15)
            score += 2;
        else if (subtotal < 15)
            fifteens(j + 1, subtotal);
    }
}

int main(void)
{
    fgets(buf, sizeof buf, stdin);
    score = 0;

    /* Read cards from buf */
    for (i = 0 ; i < 4 ; ++i)
        getcard(i * 3);
    getcard(14);

    /* Score fifteens */
    fifteens(0, 0);

    /* Score any runs and/or pairs */
    touching = 0;
    matching = 1;
    for (i = 1 ; i < 15 ; ++i) {
        if (buckets[i]) {
            score += buckets[i] * (buckets[i] - 1);
            ++touching;
            matching *= buckets[i];
        } else {
            if (touching > 2)
                score += touching * matching;
            touching = 0;
            matching = 1;
        }
    }

    /* Check for flush */
    for (i = 1 ; i < 5 && suit[i] == suit[0] ; ++i) ;
    if (i >= (buf[17] == '!' ? 5 : 4))
        score += i;

    /* Check for hisnob */
    for (i = 0 ; i < 4 ; ++i)
        if (rank[i] == 11 && suit[i] == suit[4])
            ++score;

    printf("%d\n", score ? score : 19);
    return 0;
}
\$\endgroup\$
2
  • \$\begingroup\$ Since I didn't specify, feel free to shave off those 20 chars! \$\endgroup\$
    – boothby
    Commented May 13, 2012 at 23:46
  • \$\begingroup\$ This is really impressive -- the question is big & ugly too! This being code golf, segfaulting on crap input is a-ok. \$\endgroup\$
    – boothby
    Commented May 14, 2012 at 6:17
5
\$\begingroup\$

Ruby 1.9, 359 356

It's far too long - almost as much as the C solution.

R='A23456789TJQK'
y=gets
f=y.scan /\w+/
o=f.map(&:chr).sort_by{|k|R.index k}
s=0
2.upto(5){|i|o.combination(i){|j|t=0
j.map{|k|t+=k==?A?1:k<?:?k.hex: 10}
(t==15||i<3&&j.uniq!)&&s+=2}}
m=n=l=1
(o+[z=?_]).map{|k|k[z]?n+=1:R[z+k]?(m*=n
l+=n=1):(l>2&&s+=l*m*n
l=n=m=1)
z=k}
x=f.take_while{|k|k[y[1]]}.size
x>(y[?!]?4:3)&&s+=x
y[?J+f[4][1]+' ']&&s+=1
p s>0?s:19
\$\endgroup\$
0
5
+200
\$\begingroup\$

Something to begin with.. Ruby, 422 365 355 352

c=gets
a,b=c.scan(/(\w)(\w)/).transpose
f=->x{x.uniq.size<2}
s=f[b]?5:!c[/!/]&f[b[0,4]]?4:0
c[/J(.).*\1 ?!?$/]&&s+=1
s+=[5,4,3].map{|i|a.permutation(i).map{|x|'A23456789TJQK'[x*'']?i:0}.inject :+}.find{|x|x>0}||0
a.map{|x|s+=a.count(x)-1}
2.upto(5){|i|s+=2*a.map{|x|x.tr(?A,?1).sub(/\D/,'10').to_i}.combination(i).count{|x|x.inject(:+)==15}}
p s<1?19:s

Slightly ungolfed:

def t(c)
  s=0
  
  if c.scan(/[SDHC]/).uniq.size<2 # Flush
    s+=5 
  elsif c[0..9].scan(/[SDHC]/).uniq.size<2 && c[-1]!=?! # Flush
    s+=4
  end
  s+=1 if c =~ /J(.).*(\1$|\1\s.$)/ # Nobs

  c=c.scan(/[^ \|]+/).map{|x|x[0]}[0..4]
  d = (3..5).map{|i|c.permutation(i).map{|x| 'A23456789TJQK'.include?(x*'') ? i : 0}.inject(:+)}.reverse.find{|x|x>0} || 0# Runs
  s+=d
  c.map{|x|s+=c.count(x)-1} # Pairs
  c.map!{|x|x.tr('A','1').gsub(/[JQK]/,'10').to_i}
  (2..5).map{|i|s+=2*c.combination(i).count{|x|15==x.inject(:+)}} # 15s
  s<1 ? 19 : s
end

Unit tests for golfed version:

require "test/unit"

def t(c)
c=gets
a,b=c.scan(/(\w)(\w)/).transpose
f=->x{x.uniq.size<2}
s=f[b]?5:!c[/!/]&f[b[0,4]]?4:0
c[/J(.).*\1 ?!?$/]&&s+=1
s+=[5,4,3].map{|i|a.permutation(i).map{|x|'A23456789TJQK'[x*'']?i:0}.inject :+}.find{|x|x>0}||0
a.map{|x|s+=a.count(x)-1}
2.upto(5){|i|s+=2*a.map{|x|x.tr(?A,?1).sub(/\D/,'10').to_i}.combination(i).count{|x|x.inject(:+)==15}}
p s<1?19:s
end

class Test1 < Test::Unit::TestCase
  def test_simple
    assert_equal 21, t("5S 5H 5D JS | KS")
    assert_equal 21, t("JS 5H 5D 5S | KS")
    assert_equal 12, t("JD 3C 4H 5H | 5S")
    assert_equal 13, t("9S 8S 7S 6S | 5H")
    assert_equal 14, t("8D 7D 6D 5D | 4D")
    assert_equal 19, t("AD KD 3C QD | 6D")
    assert_equal 9, t("AS 2D 3H JS | 4S !")
    assert_equal 9, t("JS 2D 3H AS | 4S !")
    assert_equal 14, t("8D 7D 6D 5D | 4D !")
    assert_equal 9, t("9S 8S 7S 6S | 5H !")
  end
end

Results:

% ruby ./crib.rb
   Run options: 

# Running tests:

21
21
12
13
14
19
9
9
14
9
.

Finished tests in 0.014529s, 68.8281 tests/s, 688.2813 assertions/s.

1 tests, 10 assertions, 0 failures, 0 errors, 0 skips
\$\endgroup\$
4
\$\begingroup\$

Python, 629 characters

I'm only posting mine because no one else has. It's pretty long :(

g=range
i=raw_input().split()
r,u=zip(*[tuple(x)for x in i if x not in'!|'])
v=map(int,[((x,10)[x in'TJQK'],1)[x=='A']for x in r])
z=list(set(map(int,[(x,dict(zip('ATJQK',[1,10,11,12,13])).get(x))[x in'ATJQK']for x in r])))
z.sort()
z=[-1]*(5-len(z))+z
s=p=l=0
for a in g(5):
 for b in g(a+1,5):
    s+=2*(v[a]+v[b]==15)
    p+=2*(r[a]==r[b])
    if z[a:b+1]==g(z[a],z[b]+1)and b-a>1:l=max(l,b+1-a)
    for c in g(b+1,5):s+=2*(v[a]+v[b]+v[c]==15)
for d in g(5):s+=2*(sum(v)-v[d]==15)
n=len(set(u))
s+=4*(n==2 and u[-1] not in u[:4] and i[-1]!='!')+5*(n<2)+('J'+u[4]in i[:4])+2*(sum(v)==15)+p+((l*3,l*p)[p<5]or l)
print(s,19)[s<1]
\$\endgroup\$
3
  • \$\begingroup\$ Wow, that is long! Nicely done, though. BTW, you never need spaces before/after quotes and brackets. \$\endgroup\$
    – boothby
    Commented Apr 22, 2012 at 18:04
  • 1
    \$\begingroup\$ Oh thanks, I forgot about that. It's slightly shorter now ;) \$\endgroup\$
    – grc
    Commented Apr 23, 2012 at 6:16
  • \$\begingroup\$ How about print s or 19? I think you can also use Python 3.x and shave off 3 more characters (raw_input to input, then print s or 19 to print(s or 19)). \$\endgroup\$
    – Ry-
    Commented Aug 4, 2012 at 19:20
2
\$\begingroup\$

GolfScript, 187 178 174 characters

:c"J"c{"SCDH"?)},1/:s-1=+/,([s)-!5*s);)-!4*c"!"?)!*]$-1=+0.14,{c{"A23456789TJQK"?)}%{},:v\{=}+,,.{@*\)}{;.2>**+1 0}if}/;;5-v{{=+}+v\/}/[0]v{.9>{;10}*{1$+}+%}/{15=},,2*+.!19*+

Since I never played cribbage I don't know any fancy scoring tricks. Therefore I thought the only way to compete (at least a little bit) is using a golf language. The code is pretty plain GolfScript, the test cases can be found here.

The code in a more readable fashion (reformatted and ungolfed a little):

# Save cards to <c>
:c;

# Is it a non-crib hand? <r>
c"!"?)!:r;

# Values go to <v>
c{"A23456789TJQK"?)}%{},:v;

# Suits go to <s>
c{"SCDH"?)},1/:s;

# Print score for Fifteens
[0]v{.9>{;10}*{1$+}+%}/{15=},,2* .p

# Print score for Pairs
-5v{{=+}+v\/}/ .p

# Print score for Runs
0..14,{v\{=}+,,.{*\)\}{;\.2>**+0 1}if}/;; .p

# Print score for Flush
[s)-!5*s);)-!4*r*]$-1= .p

# And finally print the score for Nobs
c"J"s-1=+/,( .p

# Sum up the sub-scores and if score is zero set to 19
++++
.!19*+

Edit: Changed logic for fifteens and flushes.

\$\endgroup\$
1
  • 1
    \$\begingroup\$ Yikes! That's the longest GS script I've ever seen! Bravo! \$\endgroup\$
    – boothby
    Commented Aug 4, 2012 at 19:48
2
\$\begingroup\$

Python 2, 606 584 bytes

Saved 22 bytes due to Jo King's golfing.

from itertools import*
s,S,C,E=sum,sorted,combinations,enumerate
def f(a):a=a.split();a.pop(4);e=a.pop(5)if a[-1]<"$"else 0;b=S("A23456789TJQK".index(i)for i,j in a);d=S(set(b));h=[j for i,j in a];z=len([s(k)for r in range(6)for k in C([[10,k+1][k<10]for k in b],r)if s(k)==15])*2+s(2for i,j in C(b,2)if i==j)+[4*(e<1),5][len(set(h))<2]*(len(set(h[:4]))<2)+(a[4][1]in[j for i,j in a[:4]if i=="J"])+s(reduce(lambda x,y:x*y,[b.count(k)for k in m])*len(m)for m in[d[s(x[:i]):s(x[:i])+j]for x in[[len(list(e))for i,e in groupby(j-i for i,j in E(d))]]for i,j in E(x)if j>2]);return z or 19

Try it online!

Slightly shorter than grc's answer, and takes a different route to get there.

Explanation:

    # import everything from "itertools" library. We only need "combinations" and "groupby".
from itertools import*
# alias functions to shorter names
s,S,C,E=sum,sorted,combinations,enumerate

# function f which takes the hand+up card+crib string as its argument
def f(a):
    # convert space-separated string into list of items.
    a=a.split()

    # remove the 4th index, which is always "|".
    a.pop(4)

    # change golfed by Jo King
    # if the final item in the list is a "!" (if it is <"$"), remove it from the list and assign it to variable "e".
    # otherwise, assign 0 to variable "e".
    # a non-empty string will evaluate to True and 0 will evaluate to False in IF checks later.
    e=a.pop(5)if a[-1]<"$"else 0

    # for each card in the list, split the identifiers into the value(i) and the suit(j).
    # return the value's index in the string "A23456789TJQK".
    # so, ["5S", "5H", "5D", "JS", "KS"] will return [4, 4, 4, 10, 12].
    # using the aliased built-in function sorted(), sort the list numerically ascending.
    b=S("A23456789TJQK".index(i)for i,j in a)

    # get the unique items in b, then sort the result numerically ascending.
    d=S(set(b))

    # for each card in the list, split the identifiers into the value(i) and the suit(j).
    # return the suits.
    h=[j for i,j in a]

        # fifteens
        # changes golfed by Jo King
        # generate pairs of (10, value + 1) for all cards (since they are zero-indexed)
        # since True and False evaluate to 1 and 0 in python, return 10 if k>=10
        # and reduce all values >10 to 10
        # get all unique combinations of cards for 5 cards, 4 cards, 3 cards, 2 cards, and 1 card
        # add the values of all unique combinations, and return any that equal 15
        # multiply the number of returned 15s by 2 for score
    z=len([s(k)for r in range(6)for k in C([[10,k+1][k<10]for k in b],r)if s(k)==15])*2
        +
        # pairs
        # using itertools.combinations, get all unique combinations of cards into groups of 2.
        # then, add 2 for each pair where both cards have an identical value.
        s(2for i,j in C(b,2)if i==j)
        +
        # flush
        # changes golfed by Jo King
        # using list indexing
        # [4 * (0 if crib else 1), 5], get item at index [0 if more than one suit in hand+up card else 1]
        #    -> 4 if not crib and not all suits same
        #    -> 5 if all cards same
        #    -> 0 otherwise
        # * (0 if more than one suit in hand else 1)
        #    -> 4 * 0 if not crib and not all suits same
        #    -> 4 * 1 if not crib and all suits same
        #    -> 5 * 1 if all cards same
        #    -> 0 otherwise
        [4*(e<1),5][len(set(h))<2]*(len(set(h[:4]))<2)
        +
        # nobs
        # check if the suit of the 5th card (4, zero-indexed) matches the suit of any of the other 4 cards, and if it does is that card a Jack
        (a[4][1]in[j for i,j in a[:4]if i=="J"])
        +
        # runs
        s(reduce(lambda x,y:x*y,[b.count(k)for k in m])*len(m)for m in[d[s(x[:i]):s(x[:i])+j]for x in[[len(list(e))for i,e in groupby(j-i for i,j in E(d))]]for i,j in E(x)if j>2])

    # since only 0 evaluates to false, iff z==0 return 19, else return z.
    print z or 19

Explanation for runs logic specifically:

# for each index and value in the list, add the value minus the index
# since the list is sorted and reduced to unique values, this means adjacent values will all be the same value after offset
# ex: "JD 3C 4H 5H | 5S" -> [2, 3, 4, 10] - > [2, 2, 2, 7]
z = []
for i,j in enumerate(d):
    z.append(j-i)

# group the values by unique value
# then add the length of the groups to the list
# ex: [2, 2, 2, 7] -> [2:[2,2,2], 7:[7]]
#     [2:[2,2,2], 7:[7]] -> [[3], [1]]
w = []
for i,e in groupby(z):
    w.append([len(list(e))])

# list is double-nested so that the combined list comprehension leaves "x" available in both places it is needed
z = []
for x in w:
    for i,j in enumerate(x):
        if j>2:
            # if the group length is larger than 2
            # slice the list of unique card values to obtain only run values
            # since the run can be anywhere in the list, sum the preceding lengths to find the start and end index
            a = d[ sum(x[:i]) : sum(x[:i])+j ]
            z.append(a)

w = []
for m in z:
    # get the number of times the value is in the entire hand
    # ex: "JD 3C 4H 5H | 5S" -> [2,3,4,4,10] and (2,3,4) -> [1, 1, 2]
    a = [b.count(k)for k in m]
    # multiply all values together
    # [1, 1, 2] = 1*1*2 = 2
    a = reduce(lambda x,y:x*y, a)
    # length of the run * number of duplicate values
    a *= len(m)
    w.append(a)

# sum the results of the runs
return sum(w)
\$\endgroup\$
1
  • 1
    \$\begingroup\$ Some quick golfing of the ifs to get to 584 bytes \$\endgroup\$
    – Jo King
    Commented Aug 1, 2018 at 6:46
1
\$\begingroup\$

Stax, 106 bytes

Çí╬Δ╓↔╥.L§º♦½┌§└─»◄G≤n▒HJ♀p$¼♥,Q¢▲»Δ÷♠º≈r↑Vo\b■┌4Üé∟]e:┬A½f║J4σ↔└ΓW6O?╧φ¡╫╠├√├ùß5₧k%5ê╜ò/Φ/7w╠█91I◘┬n≥ìk♂╪

Run and debug online!

Bonus for CP437: See those suits symbol in the packed Stax? Too bad that the clubs do not appear ...

The ASCII equivalent is

jc%7<~6(4|@Y{h"A23456789TJQK"I^mXS{{A|mm|+15=_%2=_:u*+f%HxS{{o:-u1]=f{%mc3+|Msn#*+y{H"SHCD"ImY:uc5*s!yNd:u;**HH++yN|Ixs@11#+c19?

Explanation

jc%7<~6(4|@Y...X...Y...c19?
j                              Split on space
 c%7<~                         Is it a crib hand? Put it on input stack for later use
      6(                       Remove "!" if it exists
        4|@                    Remove "|"
           Y                   Store list of cards in y
            ...X               Store ranks in x
                ...            Perform scoring for ranks
                   Y           Store suits in y
                    ...        Perform scoring for suits
                       c19?    If the score is 0, change it to 19

{h"..."I^mX
{        m     Map each two character string to
 h             The first character
  "..."I^      1-based index of the character in the string

S{{A|mm|+15=_%2=_:u*+f%H
S                          Powerset
 {                   f%H   Twice the number of elements that satisfy the predicate
  {A|mm                        Value of card. Take the minimum of the rank and 10
       |+15=                   Sum of values equal 15 (*)
            _%2=               Length is 2 (**)
                _:u            All elements are the same (***)
                   *+          ( (***) and (**) ) or (*)

xS{{o:-u1]=f{%mc3+|Msn#*+
xS                                Powerset of ranks
  {        f                      Filter with predicate
   {o                                 Sort
     :-u                              Unique differences between elements
        1]=                           Is [1]
            {%mc                  Length of all runs
                3+|M              Maximum of all the lengths and 3
                    sn#           Number of runs with maximal length
                       *          Multiplied by its length
                        +         Add to score

y{H"SHCD"ImY
y{        mY    For each two character string
  H"SHCD"I      0-based index of the second character in the string "SHCD"

:uc5*s!yNd:u;**HH++
:uc5*                 5 points if all cards have same suit
     s!               Not all cards have same suit (#)
       yNd:u          First four cards have same suit (##)
            ;         Not a crib hand (###)
             **HH++   4 points if (#) and (##) and (###), add to score

yN|Ixs@11#+
yN|I           Index of cards with the same suit of last card (not including itself)
    xs@        The rank at these indices
       11#     Number of Jacks with the same suit of last card
          +    Add to score
\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.