68
\$\begingroup\$

Write a program that is a polyglot in three languages that plays rock–paper–scissors.

The input for any version of the program is always one of the strings rock or paper or scissors.

In the first language the program must output the rock–paper–scissors choice that beats the input:

Input     Output
rock      paper
paper     scissors
scissors  rock

In the second language the program must output the rock–paper–scissors choice that ties the input:

Input     Output
rock      rock
paper     paper
scissors  scissors

In the third language the program must output the rock–paper–scissors choice that loses to the input:

Input     Output
rock      scissors
paper     rock
scissors  paper

The shortest code in bytes wins. Tiebreaker is higher-voted answer.

The inputs and/or outputs may optionally have a trailing newline but otherwise should only be the plain rock/paper/scissors strings. You may use uppercase ROCK, PAPER, SCISSORS if desired.

You may not use different versions of the same language (e.g. Python 2 and 3).

\$\endgroup\$
  • \$\begingroup\$ Can a language error out to exit? \$\endgroup\$ – Kritixi Lithos Apr 12 '17 at 17:39
  • 2
    \$\begingroup\$ @KritixiLithos Go with the meta concensus. "I think terminating with an error or an uncaught exception is fine here, as long as it doesn't produce stray output to STDOUT." \$\endgroup\$ – Calvin's Hobbies Apr 12 '17 at 17:42
  • 2
    \$\begingroup\$ Never quite sure with polyglots, can the different languages take input in different ways? \$\endgroup\$ – Jonathan Allan Apr 12 '17 at 21:28
  • 3
    \$\begingroup\$ @JonathanAllan That's ok. For some language sets that only have certain forms of input it would be necessary. \$\endgroup\$ – Calvin's Hobbies Apr 13 '17 at 6:31
  • \$\begingroup\$ What happened to Lizard, Spock? bigbangtheory.wikia.com/wiki/Rock_Paper_Scissors_Lizard_Spock \$\endgroup\$ – Ole Tange Apr 14 '17 at 22:16

10 Answers 10

60
\$\begingroup\$

Python, brainfuck and JavaScript, 103 99 bytes Yay under 100 bytes!

0,[.5,];p=["rock","scissors","paper"]
1//1;lambda x:p[p.index(x)-1];"""
x=>p[-~p.indexOf(x)%3]//"""

In Python, this defines a function which beats the input, in brainfuck it's just a simple cat program, and in JavaScript it loses. Here's a version which gives the functions a name, f, and also prompts for input in JavaScript and Python 3:

0,[.5,];p=["rock","scissors","paper"]
1//1;f=lambda x:p[p.index(x)-1];"""
f=x=>p[-~p.indexOf(x)%3]//"""

1//1;"""
console.log(f(prompt())) // JavaScript
1//1"""; print(f(input())) # Python

Try it online (older version): Python, brainfuck, JavaScript

Explanation:

In Python, """...""" is a multiline string, which can be used as any token. When placed stand-alone it doesn't do anything at all. I use this to "hide" the JavaScript code from Python. The same goes for the (0,[.5,]) bit, it's just a tuple containing a 0 and a list of 5, and also the 1//1 part, // in Python is integer division, but starts a comment in JavaScript. Here's the code stripped of these tokens:

p=["rock","scissors","paper"]
lambda x:p[p.index(x)-1]

The first line is pretty self-explanatory, it just defines the list p to contain the different choices in rock-paper-scissors. The second line defines an unnamed function which takes one argument, x, and gives back the choice that beats x (eg. the previous element in p)


In JavaScript, // denotes a single-line comment. Similarly to Python, single tokens are ignored, so the code stripped of these tokens is:

p=["rock","scissors","paper"]
x=>p[-~p.indexOf(x)%3]

This works similarly to Python, by first setting the list p to contain the choices and then defining an anonymous function that gives the losing choice. -~x is the same as x+1 but with higher precedence so that I can skip the parens.


In brainfuck, every character except +-,.[]<> are removed, leaving this:

,[.,][,,]
[.-]
>[-.]

The command , reads one byte of input, . prints it and [...] loops while the value is non-zero. What this program does then is read the input and printing it one character at a time until the character \0 is found. Since we don't have that in the code, we can ignore the rest of the program. In effect, this just echoes back whatever the user types in, effectively tying them.

\$\endgroup\$
  • \$\begingroup\$ Was working on a very similar solution but you beat me to it :). You have to update the Javascript TIO link btw, it is different from the other two. \$\endgroup\$ – DimP Apr 12 '17 at 17:32
  • 2
    \$\begingroup\$ x=>p[p.indexOf(x)+1]||"rock"//""" could be shortened to x=>p[(p.indexOf(x)+1)%3]//""" \$\endgroup\$ – Luke Apr 12 '17 at 18:08
  • 13
    \$\begingroup\$ +1 I've never seen Brainfuck being hidden so well. Usually it's obvious if a polyglot also contains BF. Not in this one! \$\endgroup\$ – vsz Apr 13 '17 at 6:26
  • \$\begingroup\$ I think you can move the BF program around a little to save a byte or two: 1//1,[.5,]; \$\endgroup\$ – ETHproductions Apr 14 '17 at 21:10
  • \$\begingroup\$ As a matter of fact, I think you can use the existing [] on the second line to save more bytes: 1//1,;lambda x:p[p.index(x,0)+-1];""" \$\endgroup\$ – ETHproductions Apr 14 '17 at 21:13
40
\$\begingroup\$

Python 2, Ruby, Retina, 90 83 bytes

-7 bytes thanks to Value Ink

s=['rock','paper','scissors']
print s[s.index((0and gets or input()))+(0and-2or-1)]

Try it Online: Python, Ruby, Retina

Wins in Ruby, loses in Python, and ties in Retina. This solution makes use of the fact that 0 is truthy in Ruby but falsey in Python. It also makes use of negative indexing in both Python and Ruby.

\$\endgroup\$
  • \$\begingroup\$ and has operator precedence over or, so s.index(0and STDIN.gets or input()) works. Also, gets is an alias for STDIN.gets in Ruby. \$\endgroup\$ – Value Ink Apr 12 '17 at 19:28
  • 10
    \$\begingroup\$ +1 for not just commenting out code in different ways! \$\endgroup\$ – leo Apr 12 '17 at 21:04
  • \$\begingroup\$ @ValueInk Thank you! I figured there had to be a more concise way to get input in Ruby, and it turns out there was \$\endgroup\$ – math junkie Apr 12 '17 at 21:56
21
\$\begingroup\$

V, Brain-flak, and Python 2, 97, 86, 81, 77, 75 bytes

o='rock paper scissors'.split()
lambda s:o[o.index(s)-1]#ddt.C rHd*wywVp

Two bytes saved thanks to @nmjcman101!

This was super fun! I like this answer a lot because it's a cool overview of languages I like: My favorite editor, my favorite non-esoteric language, and a language I wrote. (Technically python 3 is better, but python 2 is golfier so ¯\_(ツ)_/¯).

Try it online! in Python (slightly modified so you can see the output), which prints what loses to the input.

Try it online! in Brain-Flak, which prints what ties with the input.

Try it online! in V, which prints what beats the input.

Since V relies on unprintable ASCII characters, here is a hexdump:

00000000: 6f3d 2772 6f63 6b20 7061 7065 7220 7363  o='rock paper sc
00000010: 6973 736f 7273 272e 7370 6c69 7428 290a  issors'.split().
00000020: 6c61 6d62 6461 2073 3a6f 5b6f 2e69 6e64  lambda s:o[o.ind
00000030: 6578 2873 292d 315d 231b 6464 742e 4320  ex(s)-1]#.ddt.C 
00000040: 720e 1b48 642a 7779 7756 70              r..Hd*wywVp

Explanation:

Python

In python, this is very straightforward. We define a list of the three elements, and return the element right before the input. Since -1 returns the back element, this works circularly, and it's all very straightforward and easy. Then, everything after # is a comment.

Brain-Flak

This is also extremely straightforward in brain-flak. If we had to do winning or losing, this would probably be several hundred bytes. But this actually works in 0 bytes. On the start of the program, all of the input is loaded onto the stack. At the end of the program, the whole stack is implicitly printed.

Once we remove all of the irrelevant characters, the code brain-flak sees is

()[()]

Which simply evaluates to 1 + -1, but since this value is not used at all, it's a NOOP.

V

Here is where it gets a little weird. Naming the python list o might have seemed arbitrary, but it's definitely not. In V, o opens up a newline, and puts us in insert mode. Then,

='rock paper scissors'.split()
lambda s:o[o.index(s)-1]#

is inserted into the buffer. After that, the relevant code is:

<esc>ddxxf'C r<C-n><esc>Hd*wywVp

Explanation:

<esc>                          " Return to normal mode
     dd                        " Delete this line. Now the cursor is on '='
       t.                      " Move the cursor forward to the "'"
         C                     " Delete everything after the "'", and enter insert mode
           r                   " From insert mode, enter '<space>r'
            <C-n>              " Autocomplete the current word based on what is currently in the buffer
                               " Since only one word starts with 'r', this will insert 'rock'
                 <esc>         " Leave back to normal mode
                      H        " Go to the first line (where the input is)
                       d*      " Delete everything up until the next occurence of the input
                         w     " Move forward one word
                          yw   " Yank the word under the cursor
                            Vp " And paste that word over the current line, delete everything else
\$\endgroup\$
  • \$\begingroup\$ @WheatWizard In Python, there's no reason not too (except that it's the same length). But it ruins everything in V. \$\endgroup\$ – DJMcMayhem Apr 12 '17 at 19:22
  • \$\begingroup\$ @WheatWizard Cause V is a really weird language, and this is a really weird task for it. Everything heavily relies upon the layout of the characters, and .split() is easier to get rid of then the various parenthesis and quotes that show up in your solution. \$\endgroup\$ – DJMcMayhem Apr 12 '17 at 19:26
  • \$\begingroup\$ Super minor, but you can take out the xx and replace it with a 2 to make the command 2f' since the =' will be deleted by the d* later anyway. EDIT: you might be able to make it t.? \$\endgroup\$ – nmjcman101 Apr 13 '17 at 14:05
  • \$\begingroup\$ @nmjcman101 Oooh, sweet, awesome idea. Thanks for the tip! \$\endgroup\$ – DJMcMayhem Apr 13 '17 at 16:31
16
\$\begingroup\$

CJam, Retina, PHP, 92 86 85 bytes

ECHO["rock",0,"scissors","paper"][ORD(READLINE())%4];
#];"scissors  paper rock"S/rci=

Should be run in PHP using the -r flag.

Try it in CJam

Try it in Retina

Try it in PHP

CJam

In CJam, all capital letters are predefined variables. On the first line, many of these values are pushed onto the stack, along with some string and array literals. Some increments, decrements, and other operations are performed.

After all of that, the stack is wrapped in an array (]) and discarded (;), so none of that other stuff matters at all. The main CJam program is simply:

"scissors  paper rock"S/rci=

"scissors  paper rock"        e# Push this string
                      S/      e# Split it on spaces
                        r     e# Read the input
                         c    e# Cast to char (returns the first character in the string)
                          i   e# Cast to int (its codepoint)
                           =  e# Get the index of the split array (CJam has modular arrays)

Retina

This almost feels like cheating...

Retina will substitute any match of the regex ECHO["rock",0,"scissors","paper"][ORD(READLINE())%4]; in the input with #];"scissors paper rock"S/rci=. Whatever this regex matches, it certainly doesn't match anything in rock, paper, or scissors, so no substitution is made. The unmodified input is then implicitly output.

PHP

The second line is a comment, so it is ignored.

The first line uses the same algorithm as the CJam part, but with different ordering of the results.

\$\endgroup\$
  • 1
    \$\begingroup\$ TIL PHP functions are case insensitive. \$\endgroup\$ – gcampbell Apr 13 '17 at 13:57
14
\$\begingroup\$

C, C++, Python; 227 226 216 bytes

Saved a byte thanks to @Mat!

#include<stdio.h>/*
f=lambda a:"rock"if a[0]=="r"else"paper"if a[0]=="p"else"scissors"
"""*/
int f(char*b){puts(sizeof'b'-1?*b=='r'?"paper":*b=='s'?"rock":"scissors":*b=='r'?"scissors":*b=='s'?"paper":"rock");}
//"""

Defines a function f in all languages. Wins in C, ties in Python, loses in C++. Like C++ always does /s

The part between the /* and the */ is a comment block in C and C++ while it's the lambda function declaration in Python. It basically compares the first character of the function argument and returns the move that starts with that letter.

The part between the """s is a multiline string in Python while it's the function declaration in both C and C++. sizeof'b'-1 figures out if the current language is C of C++. It has a truthy value if the size is other than 1, a falsy value otherwise. In C character literals are 4-byte long type while in C++ they are a single byte type. Then after the language is figured out it just looks at the first letter of the input and outputs accordingly.

C

Try it online!

C++

Try it online!

Python

Try it online!

\$\endgroup\$
  • 4
    \$\begingroup\$ "The part between the """s is a comment block in Python" It's actually a multiline string. \$\endgroup\$ – ivzem Apr 12 '17 at 18:21
10
\$\begingroup\$

C++, R, C; 252 240 226 220 209 bytes

#define b/*
M=function()cat(readline())
#*/
#import<stdio.h>
#define M()main(){int i=0;char t[9];char*u;char*s[]={"rock","paper","scissors"};scanf("%s",t);for(;*t-*s[i++];);puts(s[(i+=sizeof('a')==1)%3]);}
M()

Makes use of the difference between C and C++ that the size of a character literal is 4 bytes in C and 1 byte in C++.

C++:

Try it online!

R:

Result:

> #define b/*
> M=function()cat(readline())
> #*/
> #import<stdio.h>
> #define M()main(){int i=0;char t[9];char*u;char*s[]={"rock","paper","scissors"};scanf("%s",t);for(;*t-*s[i++];);puts(s[(i+=sizeof('a')==1)%3]);}
> M()
rock
rock

C:

Try it online!

\$\endgroup\$
8
\$\begingroup\$

Gawk, Retina, Perl; 68 bytes

{eval"\$_=uc<>"}{$_=/[Sk]/?"paper":/[Pc]/?"rock":"scissors"}{print}

(with a newline at the end)

Gawk (winner)

Some junk for Perl's sake, then change the line content ($_, which is the same as $0 because the variable _ is undefined) depending on whether it contains a k or a c, then print the result. Ignore any warning about escape sequences, I meant to do that.

{a_string_that_is_ignored}
{$_ = /[Sk]/ ? "paper" : /[Pc]/ ? "rock" : "scissors"}
{print}

Retina (tie)

Same trick as Basic Sunset and others: replace matches of some silly regexp on the first line by the content of the second line, so pass the input through.

Perl (loser)

Read a line and convert it to uppercase, then choose a word based a letter it contains, and print the result. The first and last step are wrapped using eval to hide them from awk.

$_ = uc <>;
$_ = /[Sk]/ ? "paper" : /[Pc]/ ? "rock" : "scissors";
print $_

Gawk, Retina, perl -p; 57 bytes

I'm entering this as a bonus because the command line switch in perl -p is supposed to be part of the program by the usual rules on this site, which would make it not a polyglot.

{eval"\$_=uc"}$_=/[Sk]/?"paper":/[Pc]/?"rock":"scissors"

Again with a final newline for Retina. This time, with perl -p to print the output automatically, the perl overhead is significantly reduced. I can let the assignment to $_ trigger an implicit print in awk.

\$\endgroup\$
  • \$\begingroup\$ Could you perhaps add a TIO link (or a similar online-compiler to test) for each of them? \$\endgroup\$ – Kevin Cruijssen Apr 13 '17 at 11:10
  • \$\begingroup\$ @KevinCruijssen Added. The output on TIO for perl -p is empty, it must be a bug on TIO. \$\endgroup\$ – Gilles 'SO- stop being evil' Apr 13 '17 at 12:51
7
\$\begingroup\$

><>, Retina, Python 2: 144 127 123 bytes

1 byte saved thanks to @Loovjo by removing a space

4 bytes saved thanks to @mbomb007 by using input instead of raw_input

#v"PAPER"v?%4-2{"SCISSORS"v?%2:i
#>ooooo; >oooooooo<"ROCK"~<
a="KRS".index(input()[-1])
print["SCISSORS","ROCK","PAPER"][a]

Posted in TNB as a challenge, I decided to try out this combination of languages.

><>

Try it online!

The IP starts moving right.

#                      Reflect the IP so that it now moves left and it wraps around the grid
i:                     Take one character as input and duplicate it

The possible characters that will be taken into the input are PRS (since the program only takes the first character). Their ASCII-values are 80, 81 and 82.

2%                     Take the modulo 2 of the character. Yields 0, 1, 0 for P, R, S respectively
?v                     If this value is non-zero (ie the input was ROCK), go down, otherwise skip this instruction

If the input was rock, then this is what would happen:

<                      Start moving to the left
~                      Pop the top most value on the stack (which is the original value of R and not the duplicate)
"KCOR"                 Push these characters onto the stack
<                      Move left
oooo                   Output "ROCK" as characters (in turn these characters are popped)
o                      Pop the top value on the stack and output it; but since the stack is empty, the program errors out and exits promptly.

Otherwise, if the input was SCISSORS or PAPER, this is what the IP would encounter:

"SROSSICS"             Push these characters onto the stack
{                      Shift the stack, so the the original value of the first char of the input would come to the top
2-4%                   Subtract 2 and take modulo 4 of the ASCII-value (yields 2, 0 for P, S respectively)
?v                     If it is non-zero, go down, otherwise skip this instruction

If the input was PAPER, then:

>ooooooooo             Output all characters on the stack (ie "SCISSORS")
<                      Start moving left
o                      Pop a value on the stack and output it; since the stack is empty, this gives an error and the program exits.

Otherwise (if the input was SCISSORS):

"REPAP"                Push these characters onto the stack
v>ooooo;               Output them and exit the program (without any errors).

Retina

Try it online!

In this case, Retina considers each pair of two lines as a pair of a match and substitution. For example, it tries to replace anything matching the first line with the second line, but since the first line is never matched, it never substitutes it with anything, thus preserving the input.

Python 2

Try it online!

The Python program requires input to be put in between "s.

The first two lines are comments in Python.

a="KRS".index(input()[-1])             # Get the index of the last character of the input in "KRS"
print["SCISSORS","ROCK","PAPER"][a]    # Print the ath index of that array
\$\endgroup\$
  • \$\begingroup\$ I don't think the space after print at the last line is necessary. \$\endgroup\$ – Loovjo Apr 12 '17 at 17:36
  • \$\begingroup\$ You can use input() instead of raw_input(). \$\endgroup\$ – mbomb007 Apr 12 '17 at 17:47
  • \$\begingroup\$ @Loovjo Thanks for the tip :) \$\endgroup\$ – Kritixi Lithos Apr 12 '17 at 18:34
  • \$\begingroup\$ @mbomb007 That doesn't seem to work \$\endgroup\$ – Kritixi Lithos Apr 12 '17 at 18:39
  • \$\begingroup\$ @KritixiLithos it works if the python part takes input with quotes \$\endgroup\$ – undergroundmonorail Apr 12 '17 at 18:41
0
\$\begingroup\$

Ruby, Clojure, Common Lisp - 251 bytes

(print(eval '(if()({(quote SCISSORS)(quote PAPER)(quote PAPER)(quote ROCK)(quote ROCK)(quote SCISSORS)}(read))(eval(quote(nth(position(read)(quote("SCISSORS""PAPER""ROCK")):test(quote string-equal))(quote(ROCK SCISSORS PAPER))))))))
;'['"'+gets+'"']))

More readable version with whitespaces:

(print(eval '(if() ; distinguish between CLojure and Common Lisp
    ({(quote SCISSORS)(quote PAPER)(quote PAPER)
       (quote ROCK)(quote ROCK)(quote SCISSORS)}(read)) ; use hash-map as a function
    (eval(quote(nth ; find index of the input arg in the list
       (position(read)(quote("SCISSORS""PAPER""ROCK")):test(quote string-equal))  
    (quote(ROCK SCISSORS PAPER))))))))
 ;'['"'+gets+'"'])) ; ruby indexation

Clojure always wins, Ruby always draws, Common Lisp always loses.

For Ruby everything inside 's is a string. It spans across two lines. Then it uses [] operator with a string argument which return the string itself if it's present in the string. The result is printed out, Ruby just mirrors the input.

The second line is a comment for Clojure and Common Lisp. A bunch of eval and quote has to be used because Clojure needs to make sure that all symbols are valid. It would be nice to reuse code more but even nth function have different signatures in these languages. Basically for Clojure if() evaluates to true and it goes to the first branch when a hash-map of possible variants is called with argument read from stdin. Common Lisp goes to the second branch, it finds the position of the argument from stdin in the list and returns corresponding item from the resulting list.

I guess Common Lisp part can be golfed more.

See it online: Ruby , Common Lisp , Clojure

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

Scala, Javascript and Ook, 167 bytes

s=>{var a="paper,scissors,rock".split(",")/*/**/a[-1]="rock"
return a[a.indexOf(s)-1];`*/a((a.indexOf(s)+1)%3)//`//Ook. Ook. Ook! Ook? Ook. Ook! Ook! Ook. Ook? Ook!
}

Try it in Scala Try it in Javascript Try the brainfuck version of Ook

Scala - wins

s=>{                                                      //define an anonymous function
  var a="paper,scissors,rock".split(",")                  //generate the array
  /* /* */ a[-1]="rock"                                   //scala supports nested comments,
  return a[a.indexOf(s)-1];`                              //so this comment...
  */                                                      //...ends here
  a((a.indexOf(s)+1)%3)                                   //return the winning string
  //`//Ook. Ook. Ook! Ook? Ook. Ook! Ook! Ook. Ook? Ook!  //another comment
}

Javascript - loses

s=>{                                                   //define an anonymous function
  var a="paper,scissors,rock".split(",")               //generate the array
  /*/**/                                               //a comment
  a[-1]="rock"                                         //put "rock" at index -1
  return a[a.indexOf(s)-1];                            //return the string that loses
  `*/a((a.indexOf(s)+1)%3)//`                          //a string
  //Ook. Ook. Ook! Ook? Ook. Ook! Ook! Ook. Ook? Ook!  //a comment
}

Ook! - ties

The Ook part is the simple brainfuck cat program ,[.,] tranlsated to Ook.

s=>{var a="paper,scissors,rock".split(",")/*/**/a[-1]="rock"   //random stuff
return a[a.indexOf(s)-1];`*/a((a.indexOf(s)+1)%3)//`//         //more random stuff
Ook. Ook. Ook! Ook? Ook. Ook! Ook! Ook. Ook? Ook!              //the program
}                                                              //random stuff
\$\endgroup\$
  • \$\begingroup\$ If you use a[(a.indexOf(s)+2)%3] then you don't need to set a[-1]="rock". Also, can you not put the Ook code inside the JavaScript string too? \$\endgroup\$ – Neil Apr 15 '17 at 0:42

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.