24
\$\begingroup\$

There are several questions regarding this game, even a contest here. But I think all those challenges and contests need a way to automatically determine the winner of a game. So:

Challenge

Given two inputs in the range ["rock", "paper", "scissors", "lizard", "spock"] representing the selections for player 1 and player 2, determine the winner of the match.

Rules

[Winner] [action]    [loser]
-----------------------------
scissors cut         paper
paper    covers      rock
rock     crushes     lizard
lizard   poisons     spock
spock    smashes     scissors
scissors decapitates lizard
lizard   eats        paper
paper    disproves   spock
spock    vaporizes   rock
rock     crushes     scissors

Restrictions

  • Input will be a pair of strings in the given range (no other strings can be used). You can use arrays of chars if you want, as long as they represent any of the mentioned values.
  • You can choose whether to use lowercase, uppercase ("ROCK") or camel case ("Rock") for the input strings, as long as the chosen case is the same for all inputs.
  • Output will be a trio of values that determine the winner, which can be anything you want as long as the answers are consistent. Example: 1 if the first input wins, 2 if the second input wins, 0 if there is a tie. Or maybe A if the first input wins, B if the second input wins, <empty string> if there is a tie.

Goal

This is , so may the shortest program/method/function/lambda for each language win!

Tests

[Input 1] [Input 2] [Output: 1/2/0]
-----------------------------------
 rock      paper     2
 rock      scissors  1
 lizard    spock     1
 spock     rock      1
 spock     paper     2
 rock      rock      0
\$\endgroup\$
  • \$\begingroup\$ This comes from the sandbox. \$\endgroup\$ – Charlie Nov 27 '17 at 9:34
  • 1
    \$\begingroup\$ Very related. \$\endgroup\$ – Stewie Griffin Nov 27 '17 at 11:43
  • \$\begingroup\$ I've closed it as a duplicate of the linked question because its just the same question with 2 new values and a slight variation on IO. \$\endgroup\$ – Sriotchilism O'Zaic Nov 27 '17 at 15:34
  • 4
    \$\begingroup\$ @WheatWizard sometimes a slight change in the input produces very different outputs. The questions may be quite similar but the two new values create more cases to consider, so the algorithms used here are different enough to make people think again (see the answers with the cake trick). \$\endgroup\$ – Charlie Nov 27 '17 at 15:49
  • 4
    \$\begingroup\$ I agree, and voted to reopen. \$\endgroup\$ – G B Nov 27 '17 at 16:07
25
\$\begingroup\$

Python 3, 68 50 48 bytes

EDIT: Thanks to 3 tricks from Neil, and 2 from Mr. Xcoder

Each input string has a distinct fourth character, so I'm using that to distinguish them. If you arrange the elements in the cycle (scissors, paper, rock, lizard, spock), then each element beats the element directly after it and the element 3 spots to the right, cyclically. So we subtract the inputs' positions in the cycle. If that number is 0, it's a tie. If it's 1 or 3, it's a win for the first player. In my original solution, the cycle difference would index into the string "210100" to distinguish the results of the game. Neil somehow figured out this can be accomplished without indexing by adding 7 and taking the modulus by 3. Edit: Originally I used the second character to identify the string, but if you use the fourth and reverse the cycle, you get cake. And we could all use more cake.

lambda x,y,z="cake".find:(7+z(y[3])-z(x[3]))%5%3

Try it online!

Older version:

lambda x,y,z="caoip".index:(7+z(y[1])-z(x[1]))%5%3

Try it online!

Original version:

b="caoip"
def r(x,y):return"210100"[(b.index(y[1])-b.index(x[1]))%5]

Try it online!

\$\endgroup\$
  • 1
    \$\begingroup\$ 50 bytes: Try it online! \$\endgroup\$ – Neil Nov 27 '17 at 11:32
  • 6
    \$\begingroup\$ Welcome to PPCG! \$\endgroup\$ – Steadybox Nov 27 '17 at 11:40
  • 1
    \$\begingroup\$ 49 bytes: Try it online! (switching .index to .find) \$\endgroup\$ – Mr. Xcoder Nov 27 '17 at 13:03
  • 1
    \$\begingroup\$ 48 bytes: Try it online! (you do not need the p, "chaoi" suffices) \$\endgroup\$ – Mr. Xcoder Nov 27 '17 at 13:05
14
\$\begingroup\$

JavaScript (ES6), 56 bytes

Takes input in currying syntax (a)(b). Returns 0 if A wins, 1 if B wins, or false for a tie.

a=>b=>a!=b&&a>b^614>>((g=s=>parseInt(s,31)%9)(a)^g(b))&1

Demo

let f =

a=>b=>a!=b&&a>b^614>>((g=s=>parseInt(s,31)%9)(a)^g(b))&1

console.log('[Tie]');
console.log(f("paper"   )("paper"   ))

console.log('[A wins]');
console.log(f("scissors")("paper"   ))
console.log(f("paper"   )("rock"    ))
console.log(f("rock"    )("lizard"  ))
console.log(f("lizard"  )("spock"   ))
console.log(f("spock"   )("scissors"))
console.log(f("scissors")("lizard"  ))
console.log(f("lizard"  )("paper"   ))
console.log(f("paper"   )("spock"   ))
console.log(f("spock"   )("rock"    ))
console.log(f("rock"    )("scissors"))

console.log('[B wins]');
console.log(f("paper"   )("scissors"))
console.log(f("rock"    )("paper"   ))
console.log(f("lizard"  )("rock"    ))
console.log(f("spock"   )("lizard"  ))
console.log(f("scissors")("spock"   ))
console.log(f("lizard"  )("scissors"))
console.log(f("paper"   )("lizard"  ))
console.log(f("spock"   )("paper"   ))
console.log(f("rock"    )("spock"   ))
console.log(f("scissors")("rock"    ))

How?

We define the hash function H() as:

H = s => parseInt(s, 31) % 9

This gives:

s          | H(s)
-----------+-----
"rock"     |  2
"paper"    |  8
"scissors" |  1
"lizard"   |  3
"spock"    |  4

Given two inputs a and b, we consider the following statements:

  1. do we have a > b? (in lexicographical order)
  2. does b win the game?
  3. what's the value of N = H(a) XOR H(b)?

From (1) and (2), we deduce whether the result of a > b should be inverted to get the correct winner and we store this flag in the N-th bit of a lookup bitmask.

a        | H(a) | b        | H(b) | N  | a > b | b wins | invert
---------+------+----------+------+----+-------+--------+-------
rock     |   2  | paper    |   8  | 10 | Yes   | Yes    | No
rock     |   2  | scissors |   1  |  3 | No    | No     | No
rock     |   2  | lizard   |   3  |  1 | Yes   | No     | Yes
rock     |   2  | spock    |   4  |  6 | No    | Yes    | Yes
paper    |   8  | rock     |   2  | 10 | No    | No     | No
paper    |   8  | scissors |   1  |  9 | No    | Yes    | Yes
paper    |   8  | lizard   |   3  | 11 | Yes   | Yes    | No
paper    |   8  | spock    |   4  | 12 | No    | No     | No
scissors |   1  | rock     |   2  |  3 | Yes   | Yes    | No
scissors |   1  | paper    |   8  |  9 | Yes   | No     | Yes
scissors |   1  | lizard   |   3  |  2 | Yes   | No     | Yes
scissors |   1  | spock    |   4  |  5 | No    | Yes    | Yes
lizard   |   3  | rock     |   2  |  1 | No    | Yes    | Yes
lizard   |   3  | paper    |   8  | 11 | No    | No     | No
lizard   |   3  | scissors |   1  |  2 | No    | Yes    | Yes
lizard   |   3  | spock    |   4  |  7 | No    | No     | No
spock    |   4  | rock     |   2  |  6 | Yes   | No     | Yes
spock    |   4  | paper    |   8  | 12 | Yes   | Yes    | No
spock    |   4  | scissors |   1  |  5 | Yes   | No     | Yes
spock    |   4  | lizard   |   3  |  7 | Yes   | Yes    | No

Hence the bits:

bit | value
----+-----------
 0  | 0 (unused)
 1  | 1
 2  | 1
 3  | 0
 4  | 0 (unused)
 5  | 1
 6  | 1
 7  | 0
 8  | 0 (unused)
 9  | 1
10  | 0
11  | 0
12  | 0

Reading this from bottom to top and ignoring leading zeros, this gives 1001100110, or 614 in decimal.

\$\endgroup\$
5
\$\begingroup\$

05AB1E, 16 bytes

ε'³²s3èk}Æ7+5%3%

Try it online! or as a Test Suite

Explanation

Uses the very nice cake-trick from user507295

ε       }          # apply to each in the input pair
    s3è            # get the 4th letter
 '³²   k           # get its cake-index
         Æ         # reduce by subtraction
          7+       # add 7
            5%3%   # mod by 5 and then 3
\$\endgroup\$
4
\$\begingroup\$

JavaScript (ES6), 63 54 53 49 bytes

f=
(l,r,g=s=>"cake".search(s[3]))=>(7+g(r)-g(l))%5%3
<div onchange=o.textContent=`RLT`[f(a.selectedOptions[0].value,b.selectedOptions[0].value)]>L: <select id=a><option>Rock<option>Paper<option>Scissors<option>Lizard<option>Spock</select> R: <select id=b><option>Rock<option>Paper<option>Scissors<option>Lizard<option>Spock</select> Winner: <span id=o>T

Port of my golf to @WhatToDo's answer. Note: the snippet decodes the numeric result into something slightly less unreadable. Edit: Saved 1 byte thanks to @Arnauld. Saved 4 bytes thanks to @ovs.

\$\endgroup\$
  • \$\begingroup\$ @ovs Ugh, I didn't port my golf to WhatToDo's answer hard enough... \$\endgroup\$ – Neil Nov 28 '17 at 16:23
3
\$\begingroup\$

Ruby, 36 bytes

->a,b{(2+a.sum%88%6-b.sum%88%6)%5%3}

Returns 0 if 1st player wins, 1 if 2nd player wins, and 2 for a draw.

Based on user507295's answer but uses a mathematical formula to perform the hash. a.sum is the sum of all ASCII codes of the string a, mod 1<<16 and is intended as a rudimentary checksum. The hash was found using the following code:

1.upto(99){|j|p j,["scissors","paper","rock","lizard","spock"].map{|i|i.sum%j%6}}

This produced two values of j that gave a suitable hash for lowercase letters, namely 88 and 80, both of which gave the descending sequence [3,2,1,0,4] (or [4,3,2,1,0] if spock is cycled round to the beginning.)

As explained in other answers, a hash which gives a constant difference modulo 5 for consecutive elements in the above sequence is needed to make the (h[a]-h[b])%5formula work. Each element beats the element 1 or 3 places to the right and loses to the element 2 or 4 places to the right.

Try it online!

\$\endgroup\$
3
\$\begingroup\$

C, 53bytes

a="FÈ..J..ÁE";
z=*++y==*++x?0:a[*y&47>>1]>>*++x&7&1+1;

I've treated this problem as a state machine, of which there are 25 states as defined by the two, five state inputs.

By defining the results of the states within an an array of bits. I look the results up within by using unique markers within the inputs.

As noted in other solutions, characters 2, 3 and 4 are unique between the possible inputs. I have concentrated use on characters 2 and 3 which I use to select the appropriate bit within my answer array.

Within Character 2, bits 1 thru 4 clearly identify the input. By masking these bits and shifting appropriately [that's the "*y&47>>1"], the input can be noted as 0, 1, 4, 7 or 8. Hence my answer string has 9 characters. (separated interesting bits)

character 2:
a 61   011 0000 1
c 63   011 0001 1
i 69   011 0100 1
p 70   011 1000 0
o 6f   011 0111 1

Within character 3, bits 0, 1 and 2 clearly identify the input. By Masking these bits (shifting not required) [that's the "*x&7"], the input can be noted as 0, 1, 2, 3 or 7 . (separated interesting bits)

character 3
p 70   01110 000
i 69   01101 001
z 7a   01111 010
o 6f   01101 111
c 63   01100 011

The answer string can then be calculated by simply filling in the bits for the appropriate characters.

0th char represents X=paper
1st char represents X=scissors
4th char represents X=Lizard
7th char represents X=Rock
8th char represents X=Spock

0th bit represents Y=Paper
1st bit represents Y=Scissors
2nd bit represents Y=Lizard
3rd bit represents Y=Rock
7th bit represents Y=Spock

So, set bit in char where Y wins

char  7654 3210   in hex    in ascii
0     0100 0110    46         F
1     1100 1000    c8         È
2     0100 0000    d/c        .
3     0100 0000    d/c        .
4     0100 1010    4a         J
5     0100 0000    d/c        .
6     0100 0000    d/c        .
7     1100 0001    c1         Á
8     0100 0101    45         E

Then the logic is simply: if second char is same then draw, otherwise, get ascii char based on y's second character and shift bits by x's third character and add one. This makes the answers 0 for draw, 1 for x win and 2 for y win.

\$\endgroup\$
  • \$\begingroup\$ Welcome to PPCG! This is a great, well thought-out answer. \$\endgroup\$ – FantaC Nov 29 '17 at 2:35
1
\$\begingroup\$

Clojure, 130 118 bytes

-12 bytes by getting rid of my weird usage of comp.

(fn[& m](let[[p q](map #(apply +(map int(take 2 %)))m)d(- p q)](cond(= d 0)0(#{5 -16 12 -14 13 1 4 -18 2 11}d)1 1 2)))

I thought I was being clever, but this ended up being naïve compared to some other answers, and much longer.

Takes the first 2 letters of each move string, gets the char codes, and sums them. It then subtracts the sums to get d. If d is 0, its a tie (0), if it's in the set of #{5 -16 12 -14 13 1 4 -18 2 11}, p1 wins (1), else, p2 wins (2).

(defn decide [& moves] ; Using varargs so I don't need to duplicate the steps.
  ; Pop the first 2 chars of each string, convert them to their ASCII code, and sum them.
  (let [[p1 p2] (map #(apply + (map int (take 2 %))) moves)
        d (- p1 p2)]

    (cond
      (= d 0) ; A tie
      0

      (#{5 -16 12 -14 13
         1 4 -18 2 11} d) ; P1 Wins
      1

      :else ; P2 Wins
      2)))

To get the "magic numbers" that define if P1 wins, I ran

(let [ms ["rock", "paper", "scissors", "lizard", "spock"]]
  (for [p1 ms
        p2 ms]

    ; Same as above
    (let [[p q] (map #(apply + (map int (take 2 %))) [p1 p2])
          d (- p q)]

      [p1 p2 d])))

Which generates a list of d values for each possible scenario:

(["rock" "rock" 0]
 ["rock" "paper" 16]
 ["rock" "scissors" 11]
 ["rock" "lizard" 12]
 ["rock" "spock" -2]
 ["paper" "rock" -16]
 ["paper" "paper" 0]
 ["paper" "scissors" -5]
 ["paper" "lizard" -4]
 ["paper" "spock" -18]
 ["scissors" "rock" -11]
 ["scissors" "paper" 5]
 ["scissors" "scissors" 0]
 ["scissors" "lizard" 1]
 ["scissors" "spock" -13]
 ["lizard" "rock" -12]
 ["lizard" "paper" 4]
 ["lizard" "scissors" -1]
 ["lizard" "lizard" 0]
 ["lizard" "spock" -14]
 ["spock" "rock" 2]
 ["spock" "paper" 18]
 ["spock" "scissors" 13]
 ["spock" "lizard" 14]
 ["spock" "spock" 0])

Then I compared the win chart against this output. Luckily there weren't any "collisions" other than 0.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

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