16
\$\begingroup\$

Create a function that will take two strings as input and return a single output for the result. Most popular answer wins.

The rules of Rock-paper-scissors-lizard-Spock are:

  • Scissors cut paper
  • Paper covers rock
  • Rock crushes lizard
  • Lizard poisons Spock
  • Spock smashes scissors
  • Scissors decapitate lizard
  • Lizard eats paper
  • Paper disproves Spock
  • Spock vaporizes rock
  • Rock breaks scissors

The output for every possible input case is:

winner('Scissors', 'Paper') -> 'Scissors cut Paper'
winner('Scissors', 'Rock') -> 'Rock breaks Scissors'
winner('Scissors', 'Spock') -> 'Spock smashes Scissors'
winner('Scissors', 'Lizard') -> 'Scissors decapitate Lizard'
winner('Scissors', 'Scissors') -> 'Scissors tie Scissors'
winner('Paper', 'Rock') -> 'Paper covers Rock'
winner('Paper', 'Spock') -> 'Paper disproves Spock'
winner('Paper', 'Lizard') -> 'Lizard eats Paper'
winner('Paper', 'Scissors') -> 'Scissors cut Paper'
winner('Paper', 'Paper') -> 'Paper ties Paper'
winner('Rock', 'Spock') -> 'Spock vaporizes Rock'
winner('Rock', 'Lizard') -> 'Rock crushes Lizard'
winner('Rock', 'Scissors') -> 'Rock breaks Scissors'
winner('Rock', 'Paper') -> 'Paper covers Rock'
winner('Rock', 'Rock') -> 'Rock ties Rock'
winner('Lizard', 'Rock') -> 'Rock crushes Lizard'
winner('Lizard', 'Spock') -> 'Lizard poisons Spock'
winner('Lizard', 'Scissors') -> 'Scissors decapitate Lizard'
winner('Lizard', 'Paper') -> 'Lizard eats Paper'
winner('Lizard', 'Lizard') -> 'Lizard ties Lizard'
winner('Spock', 'Rock') -> 'Spock vaporizes Rock'
winner('Spock', 'Lizard') -> 'Lizard poisons Spock'
winner('Spock', 'Scissors') -> 'Spock smashes Scissors'
winner('Spock', 'Paper') -> 'Paper disproves Spock'
winner('Spock', 'Spock') -> 'Spock ties Spock'

Extra challenge suggested by @Sean Cheshire: Allow custom lists, such as those from this site. With the n-item list, the item loses to the (n-1)/2 previous, and wins over the (n-1)/2 following

\$\endgroup\$
12
  • 8
    \$\begingroup\$ Creating a 25-element lookup table isn't a challenge, and being popular isn't a code-challenge. \$\endgroup\$ Commented Jul 21, 2013 at 7:51
  • 6
    \$\begingroup\$ And when I say that being popular isn't a code-challenge: the explanation of that tag begins A code challenge is a competition for creative ways to solve a programming puzzle for an objective criterion other than code size. "Most popular answer wins" is not an objective criterion: you couldn't give the text of two answers to someone and ask them which is the most popular. \$\endgroup\$ Commented Jul 21, 2013 at 17:16
  • 1
    \$\begingroup\$ @PeterTaylor, dansalmo is right, so long as that lookup table is in a loop: this is a famous theorem of Conway: en.wikipedia.org/wiki/FRACTRAN \$\endgroup\$
    – boothby
    Commented Jul 21, 2013 at 19:00
  • 1
    \$\begingroup\$ @dansalmo The challenge you link to was created before the existence of the popularity-contest tag. \$\endgroup\$
    – primo
    Commented Jul 22, 2013 at 14:13
  • 1
    \$\begingroup\$ A suggestion to add to the challenge - Allow custom lists, such as those from this site that go up to 101 items. With the n-item list, the item loses to the (n-1)/2 previous, and wins over the (n-1)/2 follwing \$\endgroup\$
    – SeanC
    Commented Jul 22, 2013 at 14:36

14 Answers 14

13
\$\begingroup\$

APL

vs←{
    n←'Scissors' 'Paper' 'Rock' 'Lizard' 'Spock'
    x←n⍳⊂⍺ ⋄ y←n⍳⊂⍵ ⋄ X←⍺ ⋄ Y←⍵ ⋄ r←{X,⍵,⊂Y}
    x=y:     r (-x=0)↓'ties'
    y=5|1+x: r x⌷'cut' 'covers' 'crushes' 'poisons' 'smashes'
    y=5|3+x: r x⌷'decapitate' 'disproves' 'breaks' 'eats' 'vaporizes'
    ⍵∇⍺
}

Output exactly as required in all cases, including tie/ties. No lookup table, except for the actual words.

You can try it on http://ngn.github.io/apl/web/

'Spock' vs 'Paper'
Paper  disproves  Spock

APL just knows!

\$\endgroup\$
3
  • \$\begingroup\$ +1, Never noticed APL until now. Mesmerizing. Your structure is also cool. I like the last line the best. \$\endgroup\$
    – dansalmo
    Commented Jul 25, 2013 at 15:33
  • \$\begingroup\$ @dansalmo Thanks :) I like it a lot. And now thanks to github.com/ngn/apl we have an open source and web-ready interpreter (for decades there were only commercial interpreters) \$\endgroup\$
    – Tobia
    Commented Jul 25, 2013 at 16:25
  • \$\begingroup\$ @dansalmo btw, APL is a perfect fit for the kind of functional coding you seem to be doing in Python (which I like to do as well) \$\endgroup\$
    – Tobia
    Commented Jul 25, 2013 at 20:55
10
\$\begingroup\$

SED

#!/bin/sed
#expects input as 2 words, eg: scissors paper

s/^.*$/\L&/
s/$/;scissors cut paper covers rock crushes lizard poisons spock smashes scissors decapitates lizard eats paper disproves spock vaporizes rock breaks scissors/
t a
:a
s/^\(\w\+\)\s\+\(\w\+\);.*\1 \(\w\+\) \2.*$/\u\1 \3 \u\2/
s/^\(\w\+\)\s\+\(\w\+\);.*\2 \(\w\+\) \1.*$/\u\2 \3 \u\1/
t b
s/^\(\w\+\)\s\+\1;\(\1\?\(s\?\)\).*$/\u\1 tie\3 \u\1/
:b
\$\endgroup\$
1
  • 1
    \$\begingroup\$ This is... diabolical. \$\endgroup\$ Commented Jan 3, 2014 at 19:16
4
\$\begingroup\$

Here is a general solution based on a rule string of any size. It performs correct capitalization for the proper name "Spock" and also allows rules for specifying 'tie' instead of 'ties' for plural objects.

def winner(p1, p2):
    rules = ('scissors cut paper covers rock crushes lizard poisons Spock'
    ' smashes scissors decapitate lizard eats paper disproves Spock vaporizes'
    ' rock breaks scissors tie scissors'.split())

    idxs = sorted(set(i for i, x in enumerate(rules) 
                      if x.lower() in (p1.lower(), p2.lower())))
    idx = [i for i, j in zip(idxs, idxs[1:]) if j-i == 2]
    s=' '.join(rules[idx[0]:idx[0]+3] if idx 
          else (rules[idxs[0]], 'ties', rules[idxs[0]]))
    return s[0].upper()+s[1:]

Results:

>>> winner('spock', 'paper')
'Paper disproves Spock'
>>> winner('spock', 'lizard')
'Lizard poisons Spock'
>>> winner('Paper', 'lizard')
'Lizard eats paper'
>>> winner('Paper', 'Paper')
'Paper ties paper'
>>> winner('scissors',  'scissors')
'Scissors tie scissors'    
\$\endgroup\$
1
  • \$\begingroup\$ When defining rules you can use a multiline string instead of literal concatenation. This allow you to remove the redundant parentheses. \$\endgroup\$
    – Bakuriu
    Commented Jul 27, 2013 at 7:07
3
\$\begingroup\$

Python

class Participant (object):
    def __str__(self): return str(type(self)).split(".")[-1].split("'")[0]
    def is_a(self, cls): return (type(self) is cls)
    def do(self, method, victim): return "%s %ss %s" % (self, method, victim)

class Rock (Participant):
        def fight(self, opponent):
                return (self.do("break", opponent)  if opponent.is_a(Scissors) else
                        self.do("crushe", opponent) if opponent.is_a(Lizard)   else
                        None)

class Paper (Participant):
        def fight(self, opponent):
                return (self.do("cover", opponent)    if opponent.is_a(Rock)  else
                        self.do("disprove", opponent) if opponent.is_a(Spock) else
                        None)

class Scissors (Participant):
        def fight(self, opponent):
                return (self.do("cut", opponent)       if opponent.is_a(Paper)  else
                        self.do("decaitate", opponent) if opponent.is_a(Lizard) else
                        None)

class Lizard (Participant):
        def fight(self, opponent):
                return (self.do("poison", opponent) if opponent.is_a(Spock) else
                        self.do("eat", opponent)    if opponent.is_a(Paper) else
                        None)

class Spock (Participant):
        def fight(self, opponent):
                return (self.do("vaporize", opponent) if opponent.is_a(Rock)     else
                        self.do("smashe", opponent)    if opponent.is_a(Scissors) else
                        None)

def winner(a, b):
    a,b = ( eval(x+"()") for x in (a,b))
    return a.fight(b) or b.fight(a) or a.do("tie", b)
\$\endgroup\$
4
  • \$\begingroup\$ Scissors are plural, so "cuts" paper and "decaitates" lizard" is wrong (the last one misses a P too) . And "Spock smashs" should be "smashes" ;) \$\endgroup\$
    – daniero
    Commented Jul 21, 2013 at 21:50
  • \$\begingroup\$ @daniero, thanks. I noticed the scissors problem, but fixing it complicates things. Fixing "smashes" now. \$\endgroup\$
    – ugoren
    Commented Jul 22, 2013 at 8:40
  • \$\begingroup\$ @Daniel "Scissors" is plural. "Scissors" is also singular. See en.wiktionary.org/wiki/scissors \$\endgroup\$
    – DavidC
    Commented Jul 24, 2013 at 18:19
  • \$\begingroup\$ So subtle. Love it. \$\endgroup\$
    – kaoD
    Commented Jul 31, 2013 at 7:48
3
\$\begingroup\$

Ruby, arithmetic approach

The actors can can be arranged in an array in such a way that each actor a[i] wins against the actors a[i+1] and a[i+2], modulo 5, for instance:

%w(Scissors Lizard Paper Spock Rock)

Then, for an actor A with index i we can see how he matches agains actor B with index j by doing result = (j-i)%5: Result 1 and 2 means that actor A won against an actor 1 or 2 places in front of him respectively; 3 and 4 similarly means he lost against an actor behind him in the array. 0 means a tie. (Note that this may be language dependent; in Ruby (j-i)%5 == (5+j-i)%5 also when j>i.)

The most interesting part of my code is the use of this property to find a sorting function of the indices of two actors. The return value will be -1, 0 or 1 as it should:

winner,loser = [i,j].sort { |x,y| ((y-x)%5+1)/2-1 }

Here's the whole thing:

def battle p1,p2
    who = %w(Scissors Lizard Paper Spock Rock)
    how = %w(cut decapitate poisons eats covers disproves smashes vaporizes crushes breaks)
    i,j = [p1,p2].map { |s| who.find_index s }

    winner,loser = [i,j].sort { |x,y| ((y-x)%5+1)/2-1 }

    method = (winner-loser)%5/2
    what = method == 0 && "ties" || how[winner*2 + method-1]

    return "#{who[winner]} #{what} #{who[loser]}"
end
\$\endgroup\$
2
\$\begingroup\$

Python

def winner(p1, p2):
    actors = ['Paper', 'Scissors', 'Spock', 'Lizard', 'Rock']
    verbs = {'RoLi':'crushes', 'RoSc':'breaks', 'LiSp':'poisons',
             'LiPa':'eats', 'SpSc':'smashes', 'SpRo':'vaporizes', 
             'ScPa':'cut', 'ScLi':'decapitate', 'PaRo':'covers', 
             'PaSp':'disproves', 'ScSc':'tie'}
    p1, p2 = actors.index(p1), actors.index(p2)
    winner, loser = ((p1, p2), (p2, p1))[(1,0,1,0,1)[p1 - p2]]
    return ' '.join([actors[winner],
                     verbs.get(actors[winner][0:2] + actors[loser][0:2],
                               'ties'),
                     actors[loser]])
\$\endgroup\$
1
  • 1
    \$\begingroup\$ By the way, "looser" is the opposite of "tighter". "Loser" is the opposite of "winner". And it'll save you a few characters in your code. \$\endgroup\$
    – Joe
    Commented Jul 22, 2013 at 16:21
2
\$\begingroup\$

Python


  def winner(p,q):
        if p==q:
           return(' '.join([p,'tie',q]))
        d = {'ca':'cut','ao':'covers','oi':'crushes','ip':'poisons','pc': 'smashes','ci':'decapitate','ia':'eats', 'ap':'disproves', 'po':'vaporizes','oc': 'breaks'}
        [a,b] = [p[1],q[1]]
        try:
           return(' '.join([p,d[a+b],q]))
        except KeyError:
           return(' '.join([q,d[b+a],p]))

Using a tricky dictionary.

\$\endgroup\$
1
  • \$\begingroup\$ Nice one. return(' '.join([p,'tie' + 's'*(p[1]!='c'),q])) will get the verb tense correct. \$\endgroup\$
    – dansalmo
    Commented Nov 25, 2013 at 19:27
2
\$\begingroup\$

C#

Assumptions

The opponents are arranged in an n-item array where players beat the (n-1)/2 players ahead of them and lose to the (n-1)/2 players behind them. (With even length lists, the player loses to the ((n-1)/2 + 1) players behind them)

Player actions are arranged in an array where actions within the range of [(indexOfPlayer * (n-1)/2)] to [(indexOfPlayer * (n-1)/2)) + (n-2)/2 - 1].

Additional Info

CircularBuffer<T> is a wrapper around an array to create an "infinitely" addressable array. The IndexOf function returns the index of an item within the actual bounds of the array.

The Class

public class RockPaperScissors<T> where T : IComparable
{
    private CircularBuffer<T> players;
    private CircularBuffer<T> actions;

    private RockPaperScissors() { }

    public RockPaperScissors(T[] opponents, T[] actions)
    {
        this.players = new CircularBuffer<T>(opponents);
        this.actions = new CircularBuffer<T>(actions);
    }

    public string Battle(T a, T b)
    {
        int indexA = players.IndexOf(a);
        int indexB = players.IndexOf(b);

        if (indexA == -1 || indexB == -1)
        {
            return "A dark rift opens in the side of the arena.\n" +
                   "Out of it begins to crawl a creature of such unimaginable\n" +
                   "horror, that the spectators very minds are rendered\n" +
                   "but a mass of gibbering, grey jelly. The horrific creature\n" +
                   "wins the match by virtue of rendering all possible opponents\n" +
                   "completely incapable of conscious thought.";
        }

        int range = (players.Length - 1) / 2;

        if (indexA == indexB)
        {
            return "'Tis a tie!";
        }
        else
        {
            indexB = indexB < indexA ? indexB + players.Length : indexB;
            if (indexA + range < indexB)
            {
                // A Lost
                indexB = indexB >= players.Length ? indexB - players.Length : indexB;
                int actionIndex = indexB * range + (indexA > indexB ? indexA - indexB : (indexA + players.Length) - indexB) - 1;

                return players[indexB] + " " + actions[actionIndex] + " " + players[indexA];
            }
            else
            {
                // A Won
                int actionIndex = indexA * range + (indexB - indexA) - 1;

                return players[indexA] + " " + actions[actionIndex] + " " + players[indexB];
            }
        }
    }
}

Example

string[] players = new string[] { "Scissors", "Lizard", "Paper", "Spock", "Rock" };
string[] actions = new string[] { "decapitates", "cuts", "eats", "poisons", "disproves", "covers", "vaporizes", "smashes", "breaks", "crushes" };

RockPaperScissors<string> rps = new RockPaperScissors<string>(players, actions);

foreach (string player1 in players)
{
    foreach (string player2 in players)
    {
        Console.WriteLine(rps.Battle(player1, player2));
    }
}
Console.ReadKey(true);
\$\endgroup\$
1
\$\begingroup\$

Python, one-liner

winner=lambda a,b:(
    [a+" ties "+b]+
    [x for x in 
        "Scissors cut Paper,Paper covers Rock,Rock crushes Lizard,Lizard poisons Spock,Spock smashes Scissors,Scissors decapitate Lizard,Lizard eats Paper,Paper disproves Spock,Spock vaporizes Rock,Rock break Scissors"
        .split(',') 
     if a in x and b in x])[a!=b]
\$\endgroup\$
2
  • \$\begingroup\$ Very cool! You can .split(', ') and not have to jam,the,rules,together. \$\endgroup\$
    – dansalmo
    Commented Nov 14, 2013 at 2:13
  • \$\begingroup\$ @dansalmo, Thanks, But I see no harm in JammingTheRulesTogether. Though it isn't a golfing contest, I think the shorter the better. \$\endgroup\$
    – ugoren
    Commented Nov 17, 2013 at 12:32
1
\$\begingroup\$

Just a small thing I came up with:

echo "winners('Paper', 'Rock')"|sed -r ":a;s/[^ ]*'([[:alpha:]]+)'./\1/;ta;h;s/([[:alpha:]]+) ([[:alpha:]]+)/\2 \1/;G"|awk '{while(getline line<"rules"){split(line,a," ");if(match(a[1],$1)&&match(a[3],$2))print line};close("rules")}' IGNORECASE=1

Here, rules is the file containing all the rules that were given.

\$\endgroup\$
0
\$\begingroup\$

Python

Inspired by @Tobia's APL code.

def winner(p1, p2):
  x,y = map(lambda s:'  scparolisp'.find(s.lower())/2, (p1[:2], p2[:2]))
  v = (' cut covers crushes poisons smashes'.split(' ')[x*(y in (x+1, x-4))] or
       ' decapitate disproves breaks eats vaporizes'.split(' ')[x*(y in (x+3, x-2))])
  return ' '.join((p1.capitalize(), v or 'tie'+'s'*(x!=1), p2)) if v or p1==p2 \
    else winner(p2, p1)

Results:

>>> winner('Spock', 'paper')
'Paper disproves Spock'
>>> winner('Spock', 'lizard')
'Lizard poisons Spock'
>>> winner('paper', 'lizard')
'Lizard eats paper'
>>> winner('paper', 'paper')
'Paper ties paper'
>>> winner('scissors',  'scissors')
'Scissors tie scissors'    
\$\endgroup\$
0
\$\begingroup\$

C++

#include <stdio.h>
#include <string>
#include <map>
using namespace std ;
map<string,int> type = { {"Scissors",0},{"Paper",1},{"Rock",2},{"Lizard",3},{"Spock",4} };
map<pair<int,int>, string> joiner = {
  {{0,1}, " cuts "},{{0,3}, " decapitates "}, {{1,2}, " covers "},{{1,4}, " disproves "},
  {{2,3}, " crushes "},{{2,0}, " crushes "},  {{3,4}, " poisons "},{{3,1}, " eats "},
  {{4,0}, " smashes "},{{4,2}, " vaporizes "},
} ;
// return 0 if first loses, return 1 if 2nd wins
int winner( pair<int,int> p ) {
  return (p.first+1)%5!=p.second && (p.first+3)%5!=p.second ;
}
string winner( string sa, string sb ) {
  pair<int,int> pa = {type[sa],type[sb]};
  int w = winner( pa ) ;
  if( w )  swap(pa.first,pa.second), swap(sa,sb) ;
  return sa+(pa.first==pa.second?" Ties ":joiner[pa])+sb ;
}

A bit of a test

int main(int argc, const char * argv[])
{
  for( pair<const string&, int> a : type )
    for( pair<const string&, int> b : type )
      puts( winner( a.first, b.first ).c_str() ) ;
}
\$\endgroup\$
0
\$\begingroup\$

Javascript

function winner(c1,c2){
    var c = ["Scissors", "Paper", "Rock", "Lizard", "Spock"];
    var method={
        1:["cut", "covers", "crushes", "poisons", "smashes"],
        2:["decapitate", "disproves", "breaks", "eats", "vaporizes"]};
    //Initial hypothesis: first argument wins
    var win = [c.indexOf(c1),c.indexOf(c2)];
    //Check for equality
    var diff = win[0] - win[1];
    if(diff === 0){
        return c1 + ((win[0]===0)?" tie ":" ties ") + c2;
    }
    //If s is -1 we'll swap the order of win[] array
    var s = (diff>0)?1:-1;
    diff = Math.abs(diff);
    if(diff >2){
        diff = 5-diff;
        s= s * -1;
    }
    s=(diff==1)?s*-1:s;
    if(s === -1){
        win = [win[1],win[0]];
    }
    return c[win[0]] + " " + method[diff][win[0]] + " " + c[win[1]];
}
\$\endgroup\$
0
\$\begingroup\$

Javascript

I see this isn't a golfing contest, but I had been fiddling with this puzzle for a while before finding this thread, so here goes.

Here's a (standard) js version in 278 characters:

function winner(a,b){var c={rock:0,paper:1,scissors:2,spock:3,lizard:4},d="crushe,crushe,cover,disprove,cut,decapitate,smashe,vaporize,poison,eat".split(","),i=c[a],j=c[b],I=i==(j+3)%5;return i^j?i==(j+1)%5||I?a+" "+d[i*2+I]+"s "+b:b+" "+d[j*2+(j==(i+3)%5)]+"s "+a:a+" ties "+b}

Or one using E6 features (likely only works in Firefox) in 259 characters:

winner=(a,b,c={rock:0,paper:1,scissors:2,spock:3,lizard:4},d="crushe,crushe,cover,disprove,cut,decapitate,smashe,vaporize,poison,eat".split(","),i=c[a],j=c[b],I=i==(j+3)%5)=>i^j?i==(j+1)%5||I?a+" "+d[i*2+I]+"s "+b:b+" "+d[j*2+(j==(i+3)%5)]+"s "+a:a+" ties "+b
\$\endgroup\$

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