Calculate Doppelkopf Score

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.


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.


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>