Calculate Doppelkopf Score

9

Introduction

Doppelkopf is a traditional German card game for 4 players. The deck consists of 48 cards (9, 10, Jack, Queen, King, Ace of each suit while every card is in the game twice), so each player gets 12 at the start of a round.

There are always 2 teams which are determined by the distribution of the Queens of Clubs. The 2 players who are holding the Queens form a team and play against the other 2 players. The team with the Queens of Clubs are called the "Re"-team, the one without is the "Contra"-team.
At the start of the round noone knows who is on which team. The team distribution will be revealed in the progress of the round, which adds a lot of strategy to the game.

The game consists of 12 tricks. The players who wins a trick gets all the 4 cards in it. Every card has a certain value (King counts 4, Ace counts 11 for example), all cards together sum up to 240 points which is the highest possible result.

At the end of a round the points are counted and the team with the highest amount of points wins the round. Then then next round starts...

The Challenge

Every round has a certain score which is determined by the amount of points the winning team got and potential announcements. You will have to write a program that takes the point distribution and potential announcements (see explanation below) as input and outputs the score of the round and the winner.

The score calculation

As mentioned there is a Re- and a Contra-team. Also there is a maximum of 240 points possible in one round. The Re-team has to get 121 points to win, while the Contra-team only needs 120. There is also the possibility to announce "Re" or "Contra" at the start of the round if you think that you will win the game. By doing this you are raising the score.

Here are the scoring rules:

  • +1 for winning the game
  • +1 if the losing team has less than 90 points ("Keine 90")
  • +1 if the losing team has less than 60 points ("Keine 60")
  • +1 if the losing team has less than 30 points ("Keine 30")
  • +1 if the losing team has 0 points ("Schwarz")
  • +2 for an announcement of Contra
  • +2 for an announcement of Re
  • +1 if the Contra team won ("Gegen die Alten")

Note: Re/Contra-Announcements always apply, regardless of the winner. See examples in the testcases below.

Input and Output

The input to the program will be the score of the Re-team and potential announcements of Re or Contra. Since there are always 240 points in the game you can easly calculate the score of the Contra-team.

The input will be a single string which has the score of the Re-team first, followed by the potential announcements, while "R" is for Re and "C" is for Contra. If both got announced, Re will always come first.

Output will be the score of the game followed by the winning team ("C" for Contra, "R" for Re)

Rules

  • Your submission can be a full program or a function. If you choose the latter include an example on how to invoke it.
  • Input can be provided by function- or command line arguments or user input.
  • Output can be provided as return value or printed to the screen.
  • Standard loopholes apply.
  • Lowest Byte-Count wins!

Testcases

Input -> Output (Explanation)

145R  -> 3R  (Re won, +1 for winning, +2 for Re-Announcement)
120   -> 2C  (Contra won, +1 for winning, +1 for winning as Contra)
80C   -> 5C  (Contra won, +1 for winning, +1 for no 90, +1 for winning as Contra, +2 for Contra-Announcement)
240R  -> 7R  (Re won, +1 for winning, +1 for no 90, +1 for no 60, +1 for no 30, +1 for no points for the losing team, +2 for Re-announcedment)
90    -> 2C  (Contra won, +1 for winning, +1 for winning as Contra)
110RC -> 6C  (Contra won, +1 for winning, +1 for winning as Contra, +2 for Re-Announcement, +2 for Contra-Announcement)
110R  -> 4C  (Contra won, +1 for winning, +1 for winnins as Contra, +2 for Re-Announcement)
184C  -> 5R  (Re won, +1 for winning, +1 for no 90, +1 for no 60, +2 for Contra-Announcement)

Short note: I left out some rules (like solos and bonus points) on purpose to keep the challenge simple. So if you are already familiar with the game, don't be confused :)

Happy Coding!

Denker

Posted 2016-01-23T22:25:32.673

Reputation: 6 639

Answers

4

CJam, 47 46 bytes

L~]_,2*\0=:S121<:AS240S-e<_g3@30/-Te>--+A"RC"=

Run all test cases.

Hmm, conceptually this challenge is really simple, but it's surprisingly tricky to golf.

Martin Ender

Posted 2016-01-23T22:25:32.673

Reputation: 184 808

Agreed, when I tried golfing this, I came to around 100 bytes with a golfing language. I gave up very soon after. – TheCoffeeCup – 2016-01-23T23:38:13.780

4

Labyrinth, 136 103 94 92 87 84 76 74 69 61 bytes

 (}?
;)
,)
`
2
0:
 {
_-+}}82"
}"     {{"(#0/#--!.@
 -=-)}67 )

Try it online!

Ahh, it's been way too long since I used Labyrinth. It's always quite a joy to golf it. :)

Explanation

This is a bit outdated now. The overall structure of the program is still the same, but a few details have changed. I'll update this later. This is the previous solution, for reference:

 (}?
;)
,)
`
2
0 }82{_240{- ("
{ ;        #;`"~#0/#--!.@
:}-"-))}67{{
  ;#

Let's break down the score a bit:

  • If we count the characters after the number in the input, let's call it M, then there will be 2M+1 points in the final score, for winning, Re, and Contra.
  • If the score is 120 or less, Contra wins and the score is incremented by 1.
  • Looking at the losing score, we can (integer) divide it by 30 to determine the additional points. That has three problems though: 1. the value increases in the wrong direction, so we need to subtract it from the score (and add 3 to it to correct for that). 2. This gives an additional point for a result of exactly 120, which we need get rid of. 3. It doesn't handle a result of 0 (for the losing side) yet, but we can include it by turning 0 into -1 (since Labyrinth's integer division rounds towards -∞).

That's pretty much how the code works. Sp3000 posted a good primer for Labyrinth today, so I decided to steal it. :)

  • Labyrinth is stack-based and two-dimensional, with execution starting at the first recognised character. There are two stacks, a main stack and an auxiliary stack, but most operators only work on the main stack. Both stacks are bottomless, filled with zeroes.
  • At every branch of the labyrinth, the top of the stack is checked to determine where to go next. Negative is turn left, zero is straight ahead and positive is turn right.
  • Digits don't push themselves (as they do in Befunge, ><> or Prelude) - instead they "append themselves" to the top of stack, by multiplying the top by 10 and adding themselves.

So execution starts on the ( in the first row, with the instruction pointer (IP) going right. ( just turns the top of the main stack into -1 and } shifts it off to the auxiliary stack, where we can forget about it. The program really starts with the ? which reads the integer from STDIN. The IP then hits a dead end and turns around.

} shifts the input to the auxiliary stack and ( decrements another zero to -1. This will be the final score. We now enter a very tight clockwise loop:

;)
,)

The )) increments the score by two (so the first time we go through the loop, the score becomes 1, the score for winning). Then , reads a character. As long as that's not EOF, the loop continues. ; discards the character, because we don't care if it was R or C and )) adds two to the score again. This will repeat 0, 1 or 2 times depending on the input.

We leave the loop, when we've hit EOF, the top of the stack will be -1. We then run this linear part:

`20{:}-

The ` is unary negation, so we get 1, then 20 turns it into a 120. {:} gets a copy of the input integer from the auxiliary stack and - computes the difference with 120. We can use the sign of this result to determine who won.

  1. If the result is negative, Re won and we take the northern path, executing:

    ;}82{_240{-#;
    

    ; discards this result, } shifts the score away, 82 puts itself (character code of R) at the bottom of the main stack, { gets the score back on top, _240{- subtracts the integer input from 240 so we can work with Contra's (the losing side's) points.

    The #; is just to ensure that we have a non-zero value on the stack so we can join up both paths reliably.

  2. If the result is positive, Contra won and we take the southern path, executing:

    ;#"-))}67{{#;
    

    The # pushes the stack depth which is 1. This gets subtracted from the score before we add 2 with )). 67 puts itself (character code of C) at the bottom of the main stack. {{ gets the other values back.

  3. If the result is zero, Contra still won. However, that's the 120/120 case for which we need add an extra point to the final score (see above). That's why this path doesn't push a 1 with #, but instead just leaves the zero such that the - simply removes that zero from the stack.

The stacks are now joined back together and we've accounted for everything except the part where we divide by 30. But first, we need to turn a zero-score into -1. That's what this little detour does:

("
`"~

The ` doesn't affect a zero, " is a no-op and ~ is bitwise negation. The bitwise NOT of 0 is -1 as required. If the score was positive instead, then ` makes it negative, ( decrements it by 1 and ~ undoes both of those operations in one go.

#0/#--!.@

The stack depth is now 3 (the winning side's character, the total score so far, and the losing side's points), so # pushes 3. 0 turns it into a 30, / does the division. # pushes another 3 which is subtracted and that result is in turn subtracted from the final score. ! prints it as an integer, . prints the winning side as a character and @ terminates the program.

Martin Ender

Posted 2016-01-23T22:25:32.673

Reputation: 184 808

3

JavaScript (ES6), 106

Around 100 bytes with a non golfing language.

x=>([,r,t]=x.match`(\\d+)(.*)`,t=1+t.length*2,w=r>120?(r=240-r,'R'):(++t,'C'),t+!r+(r<30)+(r<60)+(r<90)+w)

Test

f=x=>(
  [,r,t]=x.match`(\\d+)(.*)`,
  t=1+t.length*2,
  w=r>120?(r=240-r,'R'):(++t,'C'),
  t+!r+(r<30)+(r<60)+(r<90)+w
)

console.log=x=>O.textContent+=x+'\n'

test=[
['145R', '3R'], //(Re won, +1 for winning, +2 for Re-Announcement)
['120',  '2C'], //(Contra won, +1 for winning, +1 for winning as Contra)
['80C',  '5C'], //(Contra won, +1 for winning, +1 for no 90, +1 for winning as Contra, +2 for Contra-Announcement)
['240R', '7R'], //(Re won, +1 for winning, +1 for no 90, +1 for no 60, +1 for no 30, +1 for no points for the losing team, +2 for Re-announcedment)
['90',   '2C'], //(Contra won, +1 for winning, +1 for winning as Contra)
['110RC','6C'], //(Contra won, +1 for winning, +1 for winning as Contra, +2 for Re-Announcement, +2 for Contra-Announcement)
['110R', '4C'], //(Contra won, +1 for winning, +1 for winnins as Contra, +2 for Re-Announcement)
['184C', '5R'], //(Re won, +1 for winning, +1 for no 90, +1 for no 60, +2 for Contra-Announcement)
]
  
test.forEach(t=>{
  var i=t[0],k=t[1],r=f(i)
console.log((k==r?'OK ':'KO ')+i+' -> '+r+(k!=r?' (expected: '+k+')':''))
})
<pre id=O></pre>

edc65

Posted 2016-01-23T22:25:32.673

Reputation: 31 086

Only around twice the byte count of the CJam answer...not bad ^^ – Denker – 2016-01-24T11:26:51.277

2

ES6, 92 bytes

s=>(p=s.match(/\d+|./g),n=+p[0],r=n>120,p.length*2-r+!(n%120)+3-((r?240-n:n)/30|0)+"CR"[+r])

p.length*2 overestimates the score by 1 if Re wins so I have to subtract r again. !(n%120)+3-((r?240-n:n)/30|0) is what I came up with to encapsulate the rules for scoring "Keine"; I so wish the boundaries had been 31, 61 and 91 as that would have made the formula 4-((r?269-n:29+n)/30|0).

Neil

Posted 2016-01-23T22:25:32.673

Reputation: 95 035