What is the score of my Scopa hand?

15

I like card game challenges so I made this one for the Italian card game Scopa. My family has been playing this game since time immemorial. It has a very interesting scoring system that should be fun to golf. I will post an answer in R to get the fun started, that I am sure people will improve upon.

The challenge: figure out the number of points scored in a round of Scopa, given the cards the player captured during the round as input.

There are 40 cards in a Scopa deck. If you are using an international deck you remove the 8s, 9s, and 10s, leaving A,2,3,4,5,6,7,Q,J,K in each suit.1 There are two players or partnerships, and after every round, all cards end up captured by one or the other of the two players. The score is counted as follows (more information here):

  • The player with the most cards scores 1 point.
  • The player with the most diamonds (or coins if using the Italian deck) scores 1 point.
  • The player with the 7 of diamonds (or coins), known as the sette bello or beautiful seven, scores 1 point.
  • The player with the highest primiera scores 1 point. A player's primiera score is the sum of the scores of the highest-value card that the player captured in each suit (see table below). If you do not have at least one card in every suit, you lose by default even if your score would exceed your opponent's score. In the exceedingly rare case that neither player has at least one card in every suit, the player with the higher primiera total scores the point.2

Table of primiera scores

| Rank  | Value |
| ----- | ----- |
| 7     | 21    |
| 6     | 18    |
| A     | 16    |
| 5     | 15    |
| 4     | 14    |
| 3     | 13    |
| 2     | 12    |
| Q,J,K | 10    |

So a player can score at most 4 points in a round.3 If there is a tie, which is possible for cards, diamonds, or primiera, no one scores the point.

It's important to realize that since each card must be captured by one of the two players, you can infer what cards the other player must have taken even if you only know what cards one player took. You will need to do this to correctly score primiera.

Challenge rules

Input

Your code should take as input the cards captured by a single player during a round of Scopa.

Input must be in string format, in which one character represents the rank of each card and one character its suit. This removes the potential loophole of passing the primiera scores in directly as input. Conversion of card ranks to primiera scores must be done in the program. However you may choose to use a single string separated by spaces or commas, an array of strings, or any other format. For example if you choose to encode ranks as 76A5432QJK and suits as DCHS you could use inputs such as ['7D', '6H', 'QD', 'JS'] or '7D,6H,QD,JS'.

Output

An integer from 0 to 4 representing the player's score.

Winning

Shortest answer in bytes wins!

Test cases

["7D", "6D", "AD", "5D", "4D", "3D", "2D", "QD", "7C", "6C", "4C", "3C", "2C", "7H", "4H", "2H", "5S", "3S", "QS", "JS", "KS"]

Scores 4: 1 point for >20 cards, 1 point for >5 diamonds, 1 point for the 7 of diamonds, and 1 point for scoring 78 in primiera (7,7,7,5 where opponent has 7,6,5,K for 64)

["3D", "7C", "6C", "AC", "5C", "4C", "3C", "2C", "QC", "4H", "7S"]

Scores 0: <=20 cards, <=5 diamonds, no 7 of diamonds, and only scores 69 in primiera (7,7,4,3 where opponent has 7,7,6,K for 70)

[7D", "6D", "AD", "5D", "4D", "3D", "2D", "7C", "6C", "AC", "5C", "4C", "3C", "2C", "7H", "6H", "AH", "5H", "4H", "3H", "2H"]

Scores 3: 1 point for >20 cards, 1 point for >5 diamonds, 1 point for 7 of diamonds. The primiera would be 63 (7,7,7) and the opponent can only score 51 (7,Q,Q,Q) but since this hand has no spades it loses the point by default.

["7D", "6D", "AD", "5D", "4D", "3D", "2D", "QD", "JD", "KD", "QC", "QH", "QS"]

Scores 3: <=20 cards, 1 point for >5 diamonds, 1 point for 7 of diamonds. The primiera only scores 51 (7,Q,Q,Q) and the opponent can score 63 (7,7,7) but since the opponent's hand has no diamonds this hand wins the primiera point by default.

["7D", "6D", "AD", "5D", "4D", "3D", "2D", "QD", "JD", "KD", "7C", "7H"]

Scores 3: <=20 cards, 1 point for >5 diamonds, 1 point for 7 of diamonds. Even though this hand has no spades, it still wins primiera by a score of 63 to 57 (7,7,7 versus 7,6,6) because the opponent's hand has no diamonds.

["7D", "6D", "AD", "5D", "4D", "3D", "2D", "QD", "JD", "KD", "QC", "QH"]

Scores 2: <=20 cards, 1 point for >5 diamonds, 1 point for 7 of diamonds. This hand has no spades, and opponent's hand has no diamonds. Opponent wins primiera by a score of 63 to 41 (7,7,7 versus 7,Q,Q).

[] (empty array)

Scores 0


1: At least in our family, Jack outranks Queen in Scopa but this is irrelevant for scoring purposes.

2: I've been playing this game since childhood and have never seen that happen but your code had better be able to handle that case!

3: There are bonus points for "sweeps" scored during the round which I am ignoring for the purpose of this challenge.

qdread

Posted 2019-09-25T19:47:01.980

Reputation: 419

1Does each rank have to be represented by a distinct character? – Doorknob – 2019-09-25T19:54:55.150

@Doorknob No not necessarily, but at least in the solution I am working on I found it necessary to have a unique character for each rank to get all the test cases right. – qdread – 2019-09-25T19:56:12.623

@Grimy good catch. thanks – qdread – 2019-09-26T15:48:35.113

Answers

6

Ruby, 156 153 bytes

->a{b='';([a[40],a.scan(/.d/)[5],a=~/;d/,'dchs'.gsub(/./){l=a.scan /.(?=#$&)/;l.size<10&&b+=(';865432'.tr(l*'','')+?0)[0];l.max}.sum>b.sum||p]-[p]).size}

Try it online!

->a{
b='';                # stores primiera of other player
([                   # this array stores all checks
a[40],               # check if >20 cards (>40 characters)
a.scan(/.d/)[5],     # check if >5 diamonds
a=~/;d/,             # check if 7 of diamonds
'dchs'.gsub(/./){    # for each suit, build a string with...
l=a.scan /.(?=#$&)/; # find all cards with this suit
l.size<10&&          # if there are less than 10, the other person has some, so
b+=                  # append to their score string the following:
(';865432'           #   start with all the cards
.tr(l*'','')         #   remove the ones we have
+?0)                 #   add back the JQK at the end
[0];                 #   take the highest
l.max}               # return the highest card that we have
.sum                 # take the sum of the codepoints
>b.sum               # check if it's greater than the other player's sum
||p                  # if not, evaluate to nil
]-[p])               # remove all nils
.size}               # count how many are left

This uses ;865432000 to represent 76A5432QJK respectively, and suits are in lowercase. (The choice of characters is because subtracting 38 from each gives their primiera value, but we never actually do so because only the relative difference matters.)

We don't check whether either player is missing a suit because it is unnecessary -- since all cards are counted as 38 plus their actual value, if someone is missing a suit, the highest score they can get is (21+38)*3 = 177, which is less than (10+38)*3 + 21+38 = 203, the lowest score the other player can get. We cannot have the two players missing an unequal nonzero number of suits, because a player can only be missing 0, 1, or 2 suits, and if someone is missing 2 suits they have all the cards of the other 2 suits.

Doorknob

Posted 2019-09-25T19:47:01.980

Reputation: 68 138

4

R, 320 298 265 238 229 224 211 209 179 bytes

This is a solution mostly due to @digEmAll, in the form of a function.

Try it online!

function(h,S=sum,A=apply,l=99+c(11,8,5:2,6,!1:3)%o%!!1:4)S(S(p<-outer(c(7:2,'A','K','J','Q'),c('D','C','H','S'),paste0)%in%h)>20,S(p[1:10])>5,p[1],S(A(l*p,2,max)-A(l*!p,2,max))>0)

Below is the best of my old mediocre attempts for 209 bytes.

edit: golfed down by aliasing some functions, then by taking Doorknob's idea of adding a constant to the score instead of checking the suits.

next edit: got rid of some redundancy and then incorporated some improvements from Giuseppe

next edit: -2 bytes thanks to digEmAll

I am terrible at this, so I'm sure someone can improve on this if they care to take the time. I feel like the sapply and function are super long and could get rid of them but I can't figure out how. Inputs are two-character strings in the standard notation.

function(h,s=sum,l=c(11,8,5:2,6,!1:3)+99)s(length(h)>20,s(grepl('D',h))>5,'7D'%in%h,s(sapply(c('D','C','H','S'),function(i,r=c(7:2,'A','K','J','Q')%in%substr(h[grep(i,h)],1,1))s(l[r][1],-l[!r][1],na.rm=T)))>0)

qdread

Posted 2019-09-25T19:47:01.980

Reputation: 419

1

You may be able to get help in the R golf chatroom, digEmAll is even a fellow Italian!

– Giuseppe – 2019-09-26T16:36:26.147

1

Just some advice but if you can swap a semi-colon just a newline (which looks to be one byte in R), it's a free swap that makes your answer more readable. Also, check out Try It Online which is an online code runner if you haven't. Not required, but again, nice to use. It can even generate CGCC posts

– Veskah – 2019-09-26T17:43:23.387

1253 bytes -- I'm not totally sure this will work, since I was mostly trying the usual set of golfs, but feel free to test and let me know. – Giuseppe – 2019-09-27T19:15:17.083

@Giuseppe thanks! I golfed it further to 240 bytes by replacing the multiple calls of match with some greps, so then I added your golfs to get it to 238! – qdread – 2019-09-27T19:48:10.570

It's recommended to post a link to TIO if possible, so people can verify your code :) (if you click on the "chain" button you can then copy the "Code Golf submission (Stack Exchange)" entry which contains the pre-compiled markdown for a standard codegolf stackexchange answer :) – digEmAll – 2019-09-29T08:19:04.773

Moved your answer into a function (same byte count) – digEmAll – 2019-09-29T08:24:06.813

1209 – digEmAll – 2019-09-29T08:46:55.457

1This should work as well for 181 – digEmAll – 2019-09-29T15:03:01.400

1@digEmAll bellissimo! :-D – qdread – 2019-09-30T00:07:22.187

@digEmAll shaved 2 bytes off by getting rid of a parentheses for 179

– qdread – 2019-10-03T12:00:25.413

@qdread: great! now just post it ! ;) – digEmAll – 2019-10-03T18:30:56.167

2

JavaScript (ES6),  171  163 bytes

Takes input as a set of cards, using their standard representation.

c=>(c.size>20)+((g=o=>[..."CHSD"].map(s=>[..."JQK2345A67"].map((v,i)=>(S=o^c.has(v+s))?m="111345679"[++n,i]||12:0,n=m=0)|(n?0:T=1,t-=m),T=t=4)|t*T)(1)>g``)+S+(n>5)

Try it online!

Commented

c =>                                // c = set of cards
  (c.size > 20) + (                 // +1 point if we have more than 20 cards
    ( g = o =>                      // g is a function taking the flag o (for 'opponent')
      [..."CHSD"].map(s =>          // for each suit s, ending with diamonds:
        [..."JQK2345A67"]           //   for each rank v at position i, sorted from
        .map((v, i) =>              //   lowest to highest primiera score:
          (S = o ^ c.has(v + s)) ?  //     if the player owns this card, set S to 1 and:
            m = "111345679"[++n, i] //       increment n; update m to the score of this
                || 12               //       rank (we use the official score - 9)
          :                         //     else:
            0,                      //       do nothing
          n = m = 0                 //     start with n = m = 0
        ) |                         //   end of inner map()
        ( n ? 0 : T = 1,            //   if n = 0, set T to 1
          t -= m ),                 //   subtract m from t
        T = t = 4                   //   start with T = t = 4
      ) | t * T                     // end of outer map(); yield t * T
    )(1) > g``                      // +1 point if g(1) is greater than g(0)
  ) +                               // (we test this way because the scores are negative)
  S +                               // +1 point if we own the 7 of diamonds
  (n > 5)                           // +1 point if we own more than 5 diamonds

Arnauld

Posted 2019-09-25T19:47:01.980

Reputation: 111 334

2

05AB1E, 41 bytes

39ÝsK‚εg9y@Oy0å•Dδ¿m(/d•₆вy.γT÷}è€àO)}`›O

Try it online or check all test cases.

Suits DCHS are respectively represented by 0123. Ranks 7A65432KJQ are respectively represented by 0123456789. Those are taken as strings, not integers, as required by the challenge (but then 05AB1E converts them to integers when needed anyway).

As in other solutions, we add a large constant (14) to each primiera score in order to make the check for missing suits unnecessary.

39Ý                      # range 0..39 (the list of all cards in the game)
   sK                    # remove all elements that appear in the input
      ‚                  # pair with the input: [player's hand, opponent's hand]

ε                     }  # map each hand to a list of its 4 subscores:
 g                       #  first subscore: length (number of cards)
 9y@O                    #  second subscore: count elements <= 9 (diamonds)
 y0å                     #  third subscore: is 0 (representing 7D) in the list
            y.γT÷}       #  group the hand by suit
 •Dδ¿m(/d•₆в      è      #  map each rank to its primiera score
                   ۈ    #  maximum primiera score in each suit
                     O   #  fourth subscore: the sum of those

`›                       # for each subscore: is player's > opponent's?
  O                      # sum
```

Grimmy

Posted 2019-09-25T19:47:01.980

Reputation: 12 521

2

MS SQL Server 2017, 525 bytes

CREATE FUNCTION f(@ NVARCHAR(MAX))RETURNS TABLE RETURN
SELECT q/21+IIF(d>6,2,IIF(d=6,1,0))+IIF(m=0,IIF(n=0 AND a>b,1,0),IIF(n=0 OR a>b,1,0))p
FROM(SELECT SUM(q)q,MAX(IIF(s='D',q,0))d,SUM(a)a,MIN(q)m,SUM(b)b,MIN(10-q)n
FROM(SELECT s,COUNT(k)q,MAX(IIF(r=k,v,0))a,MAX(IIF(r=k,0,v))b
FROM(SELECT LEFT(value,1)r,s,ASCII(RIGHT(value,1))-38 v
FROM STRING_SPLIT('7;,68,A6,5,4,3,2,Q0,J0,K0',','),(VALUES('D'),('C'),('H'),('S'))s(s))d
LEFT JOIN(SELECT LEFT(value,1)k,RIGHT(value,1)u FROM STRING_SPLIT(@,','))a
ON r+s=k+u GROUP BY s)t)t

Try it on db<>fiddle.

Andrei Odegov

Posted 2019-09-25T19:47:01.980

Reputation: 939

1

Retina 0.8.2, 334 bytes

$
 ¶234567JQKA
r`.\G
$&C $&D $&H $&S 
+`((\w\w).*¶.*)\2 
$1
T`67AJQK`8960
%O$`(\w)(\w)
$2$1
m`^(?=(...)*)(.C )*(.D )*(.H )*(.S )*
$3;$#1 $#2 $#3 $#4 $#5;${2}${3}${4}$5
m`^(?=(9D))?...;
$#1;
(;(?!.*10).* 0.*;).*
$1
\d[C-S] 
1$&
19\w 
21$*@
\d+(\w )?
$*@
(@)?;(@*) @* (@*).*;(@*)¶@?;((?!\2))?@* @* ((?!\3))?.*;((?!\4))?.*
$#1$#5$#6$#7
1

Try it online! Link includes test cases. Explanation:

$
 ¶234567JQKA
r`.\G
$&C $&D $&H $&S 

Create a list of all 40 cards.

+`((\w\w).*¶.*)\2 
$1

Remove the cards the player holds.

T`67AJQK`8960

Replace each rank by its sort order, which is 9 for 7 and 10 less than its value for other cards.

%O$`(\w)(\w)
$2$1

Sort the cards by suit and rank.

m`^(?=(...)*)(.C )*(.D )*(.H )*(.S )*
$3;$#1 $#2 $#3 $#4 $#5;${2}${3}${4}$5

Count the number of cards in each suit and also capture the highest ranked card in each suit, capturing the highest diamond twice.

m`^(?=(9D))?...;
$#1;

Check whether the highest diamond was the 7.

(;(?!.*10).* 0.*;).*
$1

Delete all of the highest cards if one of the suits has no cards.

\d[C-S] 
1$&
19\w 
21$*@
\d+(\w )?
$*@

Convert the highest cards to their unary score and sum them together. Also convert the total number of cards and suit lengths to unary.

(@)?;(@*) @* (@*).*;(@*)¶@?;((?!\2))?@* @* ((?!\3))?.*;((?!\4))?.*
$#1$#5$#6$#7

Score points if the total, diamonds, or primiera, is higher.

1

Total the score.

Neil

Posted 2019-09-25T19:47:01.980

Reputation: 95 035

1

Python 3, 249 245 239 238 bytes

-4 bytes thanks to @ovs

-6 bytes thanks to @movatica

lambda C:sum([len(C)>20,'7D'in C,len([c for c in C if'E'>c[1]])>5,p(C)>p({n+s for n in'9876543210'for s in S}-C)])
p=lambda C:[not S.strip(''.join(C)),sum(max([(c[1]==s)*int('9gcdefil99'[int(c[0])],22)for c in C]+[0])for s in S)]
S='DcHS'

Try it online!

Black Owl Kai

Posted 2019-09-25T19:47:01.980

Reputation: 980

12 bytes less with int('0734569c00'[int(x[0])],13) and if x[1]<'E' can be written as if'E'>x[1] – ovs – 2019-09-26T18:20:38.033

all(s in''.join(C)for s in S) can be shortened to not S.strip(''.join(C)), saving 6 bytes – movatica – 2019-09-28T08:44:34.713

1

C# (Visual C# Interactive Compiler), 193 bytes

x=>x.Count/21+x.Count(g=>g>48)+x.Sum(g=>g/40)/6+("".Sum(m=>{var r=x.Where(g=>g/10==m);return(r.Any()?r.Max()%10+50:0)-(r.Count()<10?"	".Max(l=>x.Contains(m*10+l)?48:l+50):0);})>0?1:0)

Try it online!

Embodiment of Ignorance

Posted 2019-09-25T19:47:01.980

Reputation: 7 014

1

AWK, 235 bytes

{s[9]=35;s[8]=32;s[7]=30;s[6]=29;s[5]=28;s[4]=27;s[3]=26;s[2]=s[1]=s[0]=24;a[$1 $2]=s[$1]}END{while(i++<4){D=0;for(j=0;j<10;j++){if(a[j i]<1){B[i]=s[j];D++}if(A[i]<a[j i])A[i]=a[j i]}x+=A[i];y+=B[i]}print(20<NR)+(D<5)+(1<a[9 4])+(y<x)}

Try it online!

Suits map to 1234 (4 is diamonds), values map to 0123456789. This program transforms the test cases into the accepted format:

BEGIN{RS=", ";FS="";t[7]=9;t[6]=8;t["A"]=7;t[5]=6;t[4]=5;t[3]=4;t[2]=3;t["Q"]=2;t["J"]=1;t["K"]=0;u["D"]=4;u["C"]=1;u["H"]=2;u["S"]=3}{gsub("[\\[\"\\]]","",$0);print t[$1],u[$2]}

My goal was just to beat the leading Python implementation :D

Daniel LaVine

Posted 2019-09-25T19:47:01.980

Reputation: 61